From 8dafe09ab25335c26e33548e988828a72e984239 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 21 Jan 2010 11:07:52 -0800 Subject: Option to log slow db queries or all db queries $config['db']['log_queries'] = true; // all $config['db']['log_slow_queries'] = 10; // queries taking > 10 seconds --- classes/Memcached_DataObject.php | 33 +++++++++++++++++++++++++++++++++ lib/default.php | 4 +++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 4ecab9db6..6ceb59e8b 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -315,6 +315,39 @@ class Memcached_DataObject extends DB_DataObject return new ArrayWrapper($cached); } + /** + * sends query to database - this is the private one that must work + * - internal functions use this rather than $this->query() + * + * Overridden to do logging. + * + * @param string $string + * @access private + * @return mixed none or PEAR_Error + */ + function _query($string) + { + $start = microtime(true); + $result = parent::_query($string); + $delta = microtime(true) - $start; + + $limit = common_config('db', 'log_slow_queries'); + if (($limit > 0 && $delta >= $limit) || common_config('db', 'log_queries')) { + $clean = $this->sanitizeQuery($string); + common_log(LOG_DEBUG, sprintf("DB query (%0.3fs): %s", $delta, $clean)); + } + return $result; + } + + // Sanitize a query for logging + // @fixme don't trim spaces in string literals + function sanitizeQuery($string) + { + $string = preg_replace('/\s+/', ' ', $string); + $string = trim($string); + return $string; + } + // We overload so that 'SET NAMES "utf8"' is called for // each connection diff --git a/lib/default.php b/lib/default.php index 5b2ae6c7c..fc6b4ab79 100644 --- a/lib/default.php +++ b/lib/default.php @@ -67,7 +67,9 @@ $default = 'db_driver' => 'DB', # XXX: JanRain libs only work with DB 'quote_identifiers' => false, 'type' => 'mysql', - 'schemacheck' => 'runtime'), // 'runtime' or 'script' + 'schemacheck' => 'runtime', // 'runtime' or 'script' + 'log_queries' => false, // true to log all DB queries + 'log_slow_queries' => 0), // if set, log queries taking over N seconds 'syslog' => array('appname' => 'statusnet', # for syslog 'priority' => 'debug', # XXX: currently ignored -- cgit v1.2.3-54-g00ecf From 4b0f953ccf7435d6ebe4a674953f62af36cc978b Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 22 Jan 2010 00:27:08 +0000 Subject: Quick hack to avoid breaking with geonames off when there's some old cookie state. This code's a little rough and tumble; any breakage halts JS execution and leaves the spinner going and no message submitted. --- js/util.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/js/util.js b/js/util.js index 88016bd6d..a749c5d9f 100644 --- a/js/util.js +++ b/js/util.js @@ -205,8 +205,10 @@ var SN = { // StatusNet cookieValue = JSON.parse(cookieValue); NLat = $('#'+SN.C.S.NoticeLat).val(cookieValue.NLat).val(); NLon = $('#'+SN.C.S.NoticeLon).val(cookieValue.NLon).val(); - NLNS = $('#'+SN.C.S.NoticeLocationNs).val(cookieValue.NLNS).val(); - NLID = $('#'+SN.C.S.NoticeLocationId).val(cookieValue.NLID).val(); + if ($('#'+SN.C.S.NoticeLocationNs).val(cookieValue.NLNS)) { + NLNS = $('#'+SN.C.S.NoticeLocationNs).val(cookieValue.NLNS).val(); + NLID = $('#'+SN.C.S.NoticeLocationId).val(cookieValue.NLID).val(); + } } if (cookieValue == 'disabled') { NDG = $('#'+SN.C.S.NoticeDataGeo).attr('checked', false).attr('checked'); @@ -301,8 +303,10 @@ var SN = { // StatusNet $('#'+SN.C.S.NoticeLat).val(NLat); $('#'+SN.C.S.NoticeLon).val(NLon); - $('#'+SN.C.S.NoticeLocationNs).val(NLNS); - $('#'+SN.C.S.NoticeLocationId).val(NLID); + if ($('#'+SN.C.S.NoticeLocationNs)) { + $('#'+SN.C.S.NoticeLocationNs).val(NLNS); + $('#'+SN.C.S.NoticeLocationId).val(NLID); + } $('#'+SN.C.S.NoticeDataGeo).attr('checked', NDG); } }); -- cgit v1.2.3-54-g00ecf From 0bb23e6fd724a12bba6766949cd3294b288d8a43 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 21 Jan 2010 16:34:26 -0800 Subject: drop debug line from xmppdaemon.php, we're done debugging that --- scripts/xmppdaemon.php | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php index 0c118c53d..cef9c4bd0 100755 --- a/scripts/xmppdaemon.php +++ b/scripts/xmppdaemon.php @@ -148,7 +148,6 @@ class XMPPDaemon extends Daemon function handle_message(&$pl) { - $this->log(LOG_DEBUG, "Received message: " . str_replace("\n", " ", var_export($pl, true))); $from = jabber_normalize_jid($pl['from']); if ($pl['type'] != 'chat') { -- cgit v1.2.3-54-g00ecf From 0e852def6ae5aa529cca0aef1187152fb5a880be Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 21 Jan 2010 16:42:50 -0800 Subject: XMPP queued output & initial retooling of DB queue manager to support non-Notice objects. Queue handlers for XMPP individual & firehose output now send their XML stanzas to another output queue instead of connecting directly to the chat server. This lets us have as many general processing threads as we need, while all actual XMPP input and output go through a single daemon with a single connection open. This avoids problems with multiple connected resources: * multiple windows shown in some chat clients (psi, gajim, kopete) * extra load on server * incoming message delivery forwarding issues Database changes: * queue_item drops 'notice_id' in favor of a 'frame' blob. This is based on Craig Andrews' work branch to generalize queues to take any object, but conservatively leaving out the serialization for now. Table updater (preserves any existing queued items) in db/rc3to09.sql Code changes to watch out for: * Queue handlers should now define a handle() method instead of handle_notice() * QueueDaemon and XmppDaemon now share common i/o (IoMaster) and respawning thread management (RespawningDaemon) infrastructure. * The polling XmppConfirmManager has been dropped, as the message is queued directly when saving IM settings. * Enable $config['queue']['debug_memory'] to output current memory usage at each run through the event loop to watch for memory leaks To do: * Adapt XMPP i/o to component connection mode for multi-site support. * XMPP input can also be broken out to a queue, which would allow the actual notice save etc to be handled by general queue threads. * Make sure there are no problems with simply pushing serialized Notice objects to queues. * Find a way to improve interactive performance of the database-backed queue handler; polling is pretty painful to XMPP. * Possibly redo the way QueueHandlers are injected into a QueueManager. The grouping used to split out the XMPP output queue is a bit awkward. --- actions/imsettings.php | 10 +- classes/Queue_item.php | 30 ++- classes/statusnet.ini | 6 +- db/08to09.sql | 16 ++ db/rc3to09.sql | 16 ++ db/statusnet.sql | 5 +- lib/dbqueuemanager.php | 140 +++------- lib/default.php | 1 + lib/iomaster.php | 39 ++- lib/jabber.php | 43 ++-- lib/jabberqueuehandler.php | 4 +- lib/ombqueuehandler.php | 2 +- lib/pingqueuehandler.php | 2 +- lib/pluginqueuehandler.php | 2 +- lib/publicqueuehandler.php | 6 +- lib/queued_xmpp.php | 117 +++++++++ lib/queuehandler.php | 95 +------ lib/queuemanager.php | 125 +++++++-- lib/smsqueuehandler.php | 2 +- lib/spawningdaemon.php | 159 ++++++++++++ lib/stompqueuemanager.php | 56 ++-- lib/util.php | 3 +- lib/xmppconfirmmanager.php | 168 ------------ lib/xmppmanager.php | 286 ++++++++++++++++++--- lib/xmppoutqueuehandler.php | 55 ++++ plugins/Enjit/enjitqueuehandler.php | 9 +- plugins/Facebook/facebookqueuehandler.php | 2 +- plugins/RSSCloud/RSSCloudPlugin.php | 41 +-- plugins/RSSCloud/RSSCloudQueueHandler.php | 50 +--- plugins/TwitterBridge/twitterqueuehandler.php | 2 +- scripts/handlequeued.php | 2 +- scripts/queuedaemon.php | 149 ++--------- scripts/xmppdaemon.php | 353 ++------------------------ 33 files changed, 946 insertions(+), 1050 deletions(-) create mode 100644 db/rc3to09.sql create mode 100644 lib/queued_xmpp.php create mode 100644 lib/spawningdaemon.php delete mode 100644 lib/xmppconfirmmanager.php create mode 100644 lib/xmppoutqueuehandler.php mode change 100755 => 100644 plugins/RSSCloud/RSSCloudQueueHandler.php diff --git a/actions/imsettings.php b/actions/imsettings.php index 751c6117c..af4915843 100644 --- a/actions/imsettings.php +++ b/actions/imsettings.php @@ -309,6 +309,8 @@ class ImsettingsAction extends ConnectSettingsAction $confirm->address_type = 'jabber'; $confirm->user_id = $user->id; $confirm->code = common_confirmation_code(64); + $confirm->sent = common_sql_now(); + $confirm->claimed = common_sql_now(); $result = $confirm->insert(); @@ -318,11 +320,9 @@ class ImsettingsAction extends ConnectSettingsAction return; } - if (!common_config('queue', 'enabled')) { - jabber_confirm_address($confirm->code, - $user->nickname, - $jabber); - } + jabber_confirm_address($confirm->code, + $user->nickname, + $jabber); $msg = sprintf(_('A confirmation code was sent '. 'to the IM address you added. '. diff --git a/classes/Queue_item.php b/classes/Queue_item.php index cf805a606..f83c2cef1 100644 --- a/classes/Queue_item.php +++ b/classes/Queue_item.php @@ -10,8 +10,8 @@ class Queue_item extends Memcached_DataObject /* the code below is auto generated do not remove the above tag */ public $__table = 'queue_item'; // table name - public $notice_id; // int(4) primary_key not_null - public $transport; // varchar(8) primary_key not_null + public $id; // int(4) primary_key not_null + public $frame; // blob not_null public $created; // datetime() not_null public $claimed; // datetime() @@ -22,14 +22,21 @@ class Queue_item extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function sequenceKey() - { return array(false, false); } - - static function top($transport=null) { + /** + * @param mixed $transports name of a single queue or array of queues to pull from + * If not specified, checks all queues in the system. + */ + static function top($transports=null) { $qi = new Queue_item(); - if ($transport) { - $qi->transport = $transport; + if ($transports) { + if (is_array($transports)) { + // @fixme use safer escaping + $list = implode("','", array_map('addslashes', $transports)); + $qi->whereAdd("transport in ('$list')"); + } else { + $qi->transport = $transports; + } } $qi->orderBy('created'); $qi->whereAdd('claimed is null'); @@ -42,7 +49,7 @@ class Queue_item extends Memcached_DataObject # XXX: potential race condition # can we force it to only update if claimed is still null # (or old)? - common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id . + common_log(LOG_INFO, 'claiming queue item id = ' . $qi->id . ' for transport ' . $qi->transport); $orig = clone($qi); $qi->claimed = common_sql_now(); @@ -57,9 +64,4 @@ class Queue_item extends Memcached_DataObject $qi = null; return null; } - - function pkeyGet($kv) - { - return Memcached_DataObject::pkeyGet('Queue_item', $kv); - } } diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 73727a6d6..6ce4495be 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -396,14 +396,14 @@ tagged = K tag = K [queue_item] -notice_id = 129 +id = 129 +frame = 66 transport = 130 created = 142 claimed = 14 [queue_item__keys] -notice_id = K -transport = K +id = K [related_group] group_id = 129 diff --git a/db/08to09.sql b/db/08to09.sql index d9c25bc72..b10e47dbc 100644 --- a/db/08to09.sql +++ b/db/08to09.sql @@ -94,3 +94,19 @@ create table user_location_prefs ( constraint primary key (user_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; +create table queue_item_new ( + id integer auto_increment primary key comment 'unique identifier', + frame blob not null comment 'data: object reference or opaque string', + transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...', + created datetime not null comment 'date this record was created', + claimed datetime comment 'date this item was claimed', + + index queue_item_created_idx (created) + +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +insert into queue_item_new (frame,transport,created,claimed) + select notice_id,transport,created,claimed from queue_item; +alter table queue_item rename to queue_item_old; +alter table queue_item_new rename to queue_item; + diff --git a/db/rc3to09.sql b/db/rc3to09.sql new file mode 100644 index 000000000..02dc7a6e2 --- /dev/null +++ b/db/rc3to09.sql @@ -0,0 +1,16 @@ +create table queue_item_new ( + id integer auto_increment primary key comment 'unique identifier', + frame blob not null comment 'data: object reference or opaque string', + transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...', + created datetime not null comment 'date this record was created', + claimed datetime comment 'date this item was claimed', + + index queue_item_created_idx (created) + +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +insert into queue_item_new (frame,transport,created,claimed) + select notice_id,transport,created,claimed from queue_item; +alter table queue_item rename to queue_item_old; +alter table queue_item_new rename to queue_item; + diff --git a/db/statusnet.sql b/db/statusnet.sql index cb33ccf33..a9ed66cb4 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -244,13 +244,12 @@ create table remember_me ( ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; create table queue_item ( - - notice_id integer not null comment 'notice queued' references notice (id), + id integer auto_increment primary key comment 'unique identifier', + frame blob not null comment 'data: object reference or opaque string', transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...', created datetime not null comment 'date this record was created', claimed datetime comment 'date this item was claimed', - constraint primary key (notice_id, transport), index queue_item_created_idx (created) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; diff --git a/lib/dbqueuemanager.php b/lib/dbqueuemanager.php index 889365b64..c6350fc66 100644 --- a/lib/dbqueuemanager.php +++ b/lib/dbqueuemanager.php @@ -31,19 +31,17 @@ class DBQueueManager extends QueueManager { /** - * Saves a notice object reference into the queue item table. + * Saves an object reference into the queue item table. * @return boolean true on success * @throws ServerException on failure */ public function enqueue($object, $queue) { - $notice = $object; - $qi = new Queue_item(); - $qi->notice_id = $notice->id; + $qi->frame = $this->encode($object); $qi->transport = $queue; - $qi->created = $notice->created; + $qi->created = common_sql_now(); $result = $qi->insert(); if (!$result) { @@ -57,146 +55,92 @@ class DBQueueManager extends QueueManager } /** - * Poll every minute for new events during idle periods. + * Poll every 10 seconds for new events during idle periods. * We'll look in more often when there's data available. * * @return int seconds */ public function pollInterval() { - return 60; + return 10; } /** * Run a polling cycle during idle processing in the input loop. - * @return boolean true if we had a hit + * @return boolean true if we should poll again for more data immediately */ public function poll() { $this->_log(LOG_DEBUG, 'Checking for notices...'); - $item = $this->_nextItem(); - if ($item === false) { + $qi = Queue_item::top($this->getQueues()); + if (empty($qi)) { $this->_log(LOG_DEBUG, 'No notices waiting; idling.'); return false; } - if ($item === true) { - // We dequeued an entry for a deleted or invalid notice. - // Consider it a hit for poll rate purposes. - return true; - } - list($queue, $notice) = $item; - $this->_log(LOG_INFO, 'Got notice '. $notice->id . ' for transport ' . $queue); - - // Yay! Got one! - $handler = $this->getHandler($queue); - if ($handler) { - if ($handler->handle_notice($notice)) { - $this->_log(LOG_INFO, "[$queue:notice $notice->id] Successfully handled notice"); - $this->_done($notice, $queue); + $queue = $qi->transport; + $item = $this->decode($qi->frame); + + if ($item) { + $rep = $this->logrep($item); + $this->_log(LOG_INFO, "Got $rep for transport $queue"); + + $handler = $this->getHandler($queue); + if ($handler) { + if ($handler->handle($item)) { + $this->_log(LOG_INFO, "[$queue:$rep] Successfully handled item"); + $this->_done($qi); + } else { + $this->_log(LOG_INFO, "[$queue:$rep] Failed to handle item"); + $this->_fail($qi); + } } else { - $this->_log(LOG_INFO, "[$queue:notice $notice->id] Failed to handle notice"); - $this->_fail($notice, $queue); + $this->_log(LOG_INFO, "[$queue:$rep] No handler for queue $queue; discarding."); + $this->_done($qi); } } else { - $this->_log(LOG_INFO, "[$queue:notice $notice->id] No handler for queue $queue; discarding."); - $this->_done($notice, $queue); + $this->_log(LOG_INFO, "[$queue] Got empty/deleted item, discarding"); + $this->_fail($qi); } return true; } - /** - * Pop the oldest unclaimed item off the queue set and claim it. - * - * @return mixed false if no items; true if bogus hit; otherwise array(string, Notice) - * giving the queue transport name. - */ - protected function _nextItem() - { - $start = time(); - $result = null; - - $qi = Queue_item::top(); - if (empty($qi)) { - return false; - } - - $queue = $qi->transport; - $notice = Notice::staticGet('id', $qi->notice_id); - if (empty($notice)) { - $this->_log(LOG_INFO, "[$queue:notice $notice->id] dequeued non-existent notice"); - $qi->delete(); - return true; - } - - $result = $notice; - return array($queue, $notice); - } - /** * Delete our claimed item from the queue after successful processing. * - * @param Notice $object - * @param string $queue + * @param QueueItem $qi */ - protected function _done($object, $queue) + protected function _done($qi) { - // XXX: right now, we only handle notices - - $notice = $object; - - $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id, - 'transport' => $queue)); + $queue = $qi->transport; - if (empty($qi)) { - $this->_log(LOG_INFO, "[$queue:notice $notice->id] Cannot find queue item"); - } else { - if (empty($qi->claimed)) { - $this->_log(LOG_WARNING, "[$queue:notice $notice->id] Reluctantly releasing unclaimed queue item"); - } - $qi->delete(); - $qi->free(); + if (empty($qi->claimed)) { + $this->_log(LOG_WARNING, "Reluctantly releasing unclaimed queue item $qi->id from $qi->queue"); } + $qi->delete(); - $this->_log(LOG_INFO, "[$queue:notice $notice->id] done with item"); $this->stats('handled', $queue); - - $notice->free(); } /** * Free our claimed queue item for later reprocessing in case of * temporary failure. * - * @param Notice $object - * @param string $queue + * @param QueueItem $qi */ - protected function _fail($object, $queue) + protected function _fail($qi) { - // XXX: right now, we only handle notices - - $notice = $object; - - $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id, - 'transport' => $queue)); + $queue = $qi->transport; - if (empty($qi)) { - $this->_log(LOG_INFO, "[$queue:notice $notice->id] Cannot find queue item"); + if (empty($qi->claimed)) { + $this->_log(LOG_WARNING, "[$queue:item $qi->id] Ignoring failure for unclaimed queue item"); } else { - if (empty($qi->claimed)) { - $this->_log(LOG_WARNING, "[$queue:notice $notice->id] Ignoring failure for unclaimed queue item"); - } else { - $orig = clone($qi); - $qi->claimed = null; - $qi->update($orig); - $qi = null; - } + $orig = clone($qi); + $qi->claimed = null; + $qi->update($orig); } - $this->_log(LOG_INFO, "[$queue:notice $notice->id] done with queue item"); $this->stats('error', $queue); - - $notice->free(); } protected function _log($level, $msg) diff --git a/lib/default.php b/lib/default.php index fc6b4ab79..0cb70de2d 100644 --- a/lib/default.php +++ b/lib/default.php @@ -83,6 +83,7 @@ $default = 'stomp_password' => null, 'monitor' => null, // URL to monitor ping endpoint (work in progress) 'softlimit' => '90%', // total size or % of memory_limit at which to restart queue threads gracefully + 'debug_memory' => false, // true to spit memory usage to log ), 'license' => array('type' => 'cc', # can be 'cc', 'allrightsreserved', 'private' diff --git a/lib/iomaster.php b/lib/iomaster.php index ce77b53b2..004e92b3e 100644 --- a/lib/iomaster.php +++ b/lib/iomaster.php @@ -27,7 +27,7 @@ * @link http://status.net/ */ -class IoMaster +abstract class IoMaster { public $id; @@ -66,23 +66,18 @@ class IoMaster if ($site != common_config('site', 'server')) { StatusNet::init($site); } - - $classes = array(); - if (Event::handle('StartIoManagerClasses', array(&$classes))) { - $classes[] = 'QueueManager'; - if (common_config('xmpp', 'enabled') && !defined('XMPP_EMERGENCY_FLAG')) { - $classes[] = 'XmppManager'; // handles pings/reconnects - $classes[] = 'XmppConfirmManager'; // polls for outgoing confirmations - } - } - Event::handle('EndIoManagerClasses', array(&$classes)); - - foreach ($classes as $class) { - $this->instantiate($class); - } + $this->initManagers(); } } + /** + * Initialize IoManagers for the currently configured site + * which are appropriate to this instance. + * + * Pass class names into $this->instantiate() + */ + abstract function initManagers(); + /** * Pull all local sites from status_network table. * @return array of hostnames @@ -170,7 +165,7 @@ class IoMaster $write = array(); $except = array(); $this->logState('listening'); - common_log(LOG_INFO, "Waiting up to $timeout seconds for socket data..."); + common_log(LOG_DEBUG, "Waiting up to $timeout seconds for socket data..."); $ready = stream_select($read, $write, $except, $timeout, 0); if ($ready === false) { @@ -190,7 +185,7 @@ class IoMaster if ($timeout > 0 && empty($sockets)) { // If we had no listeners, sleep until the pollers' next requested wakeup. - common_log(LOG_INFO, "Sleeping $timeout seconds until next poll cycle..."); + common_log(LOG_DEBUG, "Sleeping $timeout seconds until next poll cycle..."); $this->logState('sleep'); sleep($timeout); } @@ -207,6 +202,8 @@ class IoMaster if ($usage > $memoryLimit) { common_log(LOG_INFO, "Queue thread hit soft memory limit ($usage > $memoryLimit); gracefully restarting."); break; + } else if (common_config('queue', 'debug_memory')) { + common_log(LOG_DEBUG, "Memory usage $usage"); } } } @@ -223,8 +220,7 @@ class IoMaster { $softLimit = trim(common_config('queue', 'softlimit')); if (substr($softLimit, -1) == '%') { - $limit = trim(ini_get('memory_limit')); - $limit = $this->parseMemoryLimit($limit); + $limit = $this->parseMemoryLimit(ini_get('memory_limit')); if ($limit > 0) { return intval(substr($softLimit, 0, -1) * $limit / 100); } else { @@ -242,9 +238,10 @@ class IoMaster * @param string $mem * @return int */ - protected function parseMemoryLimit($mem) + public function parseMemoryLimit($mem) { // http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes + $mem = strtolower(trim($mem)); $size = array('k' => 1024, 'm' => 1024*1024, 'g' => 1024*1024*1024); @@ -253,7 +250,7 @@ class IoMaster } else if (is_numeric($mem)) { return intval($mem); } else { - $mult = strtolower(substr($mem, -1)); + $mult = substr($mem, -1); if (isset($size[$mult])) { return substr($mem, 0, -1) * $size[$mult]; } else { diff --git a/lib/jabber.php b/lib/jabber.php index 4cdfa6746..b6b23521b 100644 --- a/lib/jabber.php +++ b/lib/jabber.php @@ -85,6 +85,27 @@ class Sharing_XMPP extends XMPPHP_XMPP } } +/** + * Build an XMPP proxy connection that'll save outgoing messages + * to the 'xmppout' queue to be picked up by xmppdaemon later. + */ +function jabber_proxy() +{ + $proxy = new Queued_XMPP(common_config('xmpp', 'host') ? + common_config('xmpp', 'host') : + common_config('xmpp', 'server'), + common_config('xmpp', 'port'), + common_config('xmpp', 'user'), + common_config('xmpp', 'password'), + common_config('xmpp', 'resource') . 'daemon', + common_config('xmpp', 'server'), + common_config('xmpp', 'debug') ? + true : false, + common_config('xmpp', 'debug') ? + XMPPHP_Log::LEVEL_VERBOSE : null); + return $proxy; +} + /** * Lazy-connect the configured Jabber account to the configured server; * if already opened, the same connection will be returned. @@ -143,7 +164,7 @@ function jabber_connect($resource=null) } /** - * send a single notice to a given Jabber address + * Queue send for a single notice to a given Jabber address * * @param string $to JID to send the notice to * @param Notice $notice notice to send @@ -153,10 +174,7 @@ function jabber_connect($resource=null) function jabber_send_notice($to, $notice) { - $conn = jabber_connect(); - if (!$conn) { - return false; - } + $conn = jabber_proxy(); $profile = Profile::staticGet($notice->profile_id); if (!$profile) { common_log(LOG_WARNING, 'Refusing to send notice with ' . @@ -221,10 +239,7 @@ function jabber_format_entry($profile, $notice) function jabber_send_message($to, $body, $type='chat', $subject=null) { - $conn = jabber_connect(); - if (!$conn) { - return false; - } + $conn = jabber_proxy(); $conn->message($to, $body, $type, $subject); return true; } @@ -319,7 +334,7 @@ function jabber_special_presence($type, $to=null, $show=null, $status=null) } /** - * broadcast a notice to all subscribers and reply recipients + * Queue broadcast of a notice to all subscribers and reply recipients * * This function will send a notice to all subscribers on the local server * who have Jabber addresses, and have Jabber notification enabled, and @@ -354,7 +369,7 @@ function jabber_broadcast_notice($notice) $sent_to = array(); - $conn = jabber_connect(); + $conn = jabber_proxy(); $ni = $notice->whoGets(); @@ -389,14 +404,13 @@ function jabber_broadcast_notice($notice) 'Sending notice ' . $notice->id . ' to ' . $user->jabber, __FILE__); $conn->message($user->jabber, $msg, 'chat', null, $entry); - $conn->processTime(0); } return true; } /** - * send a notice to all public listeners + * Queue send of a notice to all public listeners * * For notices that are generated on the local system (by users), we can optionally * forward them to remote listeners by XMPP. @@ -429,7 +443,7 @@ function jabber_public_notice($notice) $msg = jabber_format_notice($profile, $notice); $entry = jabber_format_entry($profile, $notice); - $conn = jabber_connect(); + $conn = jabber_proxy(); foreach ($public as $address) { common_log(LOG_INFO, @@ -437,7 +451,6 @@ function jabber_public_notice($notice) ' to public listener ' . $address, __FILE__); $conn->message($address, $msg, 'chat', null, $entry); - $conn->processTime(0); } $profile->free(); } diff --git a/lib/jabberqueuehandler.php b/lib/jabberqueuehandler.php index b1518866d..83471f2df 100644 --- a/lib/jabberqueuehandler.php +++ b/lib/jabberqueuehandler.php @@ -34,14 +34,14 @@ class JabberQueueHandler extends QueueHandler return 'jabber'; } - function handle_notice($notice) + function handle($notice) { require_once(INSTALLDIR.'/lib/jabber.php'); try { return jabber_broadcast_notice($notice); } catch (XMPPHP_Exception $e) { $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - exit(1); + return false; } } } diff --git a/lib/ombqueuehandler.php b/lib/ombqueuehandler.php index 3ffc1313b..24896c784 100644 --- a/lib/ombqueuehandler.php +++ b/lib/ombqueuehandler.php @@ -36,7 +36,7 @@ class OmbQueueHandler extends QueueHandler * @fixme doesn't currently report failure back to the queue manager * because omb_broadcast_notice() doesn't report it to us */ - function handle_notice($notice) + function handle($notice) { if ($this->is_remote($notice)) { $this->log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id); diff --git a/lib/pingqueuehandler.php b/lib/pingqueuehandler.php index 8bb218078..4e4d74cb1 100644 --- a/lib/pingqueuehandler.php +++ b/lib/pingqueuehandler.php @@ -30,7 +30,7 @@ class PingQueueHandler extends QueueHandler { return 'ping'; } - function handle_notice($notice) { + function handle($notice) { require_once INSTALLDIR . '/lib/ping.php'; return ping_broadcast_notice($notice); } diff --git a/lib/pluginqueuehandler.php b/lib/pluginqueuehandler.php index 24d504699..9653ccad4 100644 --- a/lib/pluginqueuehandler.php +++ b/lib/pluginqueuehandler.php @@ -42,7 +42,7 @@ class PluginQueueHandler extends QueueHandler return 'plugin'; } - function handle_notice($notice) + function handle($notice) { Event::handle('HandleQueuedNotice', array(&$notice)); return true; diff --git a/lib/publicqueuehandler.php b/lib/publicqueuehandler.php index 9ea9ee73a..c9edb8d5d 100644 --- a/lib/publicqueuehandler.php +++ b/lib/publicqueuehandler.php @@ -23,7 +23,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { /** * Queue handler for pushing new notices to public XMPP subscribers. - * @fixme correct this exception handling */ class PublicQueueHandler extends QueueHandler { @@ -33,15 +32,14 @@ class PublicQueueHandler extends QueueHandler return 'public'; } - function handle_notice($notice) + function handle($notice) { require_once(INSTALLDIR.'/lib/jabber.php'); try { return jabber_public_notice($notice); } catch (XMPPHP_Exception $e) { $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - die($e->getMessage()); + return false; } - return true; } } diff --git a/lib/queued_xmpp.php b/lib/queued_xmpp.php new file mode 100644 index 000000000..4b890c4ca --- /dev/null +++ b/lib/queued_xmpp.php @@ -0,0 +1,117 @@ +. + * + * @category Network + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/jabber.php'; + +class Queued_XMPP extends XMPPHP_XMPP +{ + /** + * Constructor + * + * @param string $host + * @param integer $port + * @param string $user + * @param string $password + * @param string $resource + * @param string $server + * @param boolean $printlog + * @param string $loglevel + */ + public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) + { + parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel); + // Normally the fulljid isn't filled out until resource binding time; + // we need to save it here since we're not talking to a real server. + $this->fulljid = "{$this->basejid}/{$this->resource}"; + } + + /** + * Send a formatted message to the outgoing queue for later forwarding + * to a real XMPP connection. + * + * @param string $msg + */ + public function send($msg, $timeout=NULL) + { + $qm = QueueManager::get(); + $qm->enqueue(strval($msg), 'xmppout'); + } + + /** + * Since we'll be getting input through a queue system's run loop, + * we'll process one standalone message at a time rather than our + * own XMPP message pump. + * + * @param string $message + */ + public function processMessage($message) { + $frame = array_shift($this->frames); + xml_parse($this->parser, $frame->body, false); + } + + //@{ + /** + * Stream i/o functions disabled; push input through processMessage() + */ + public function connect($timeout = 30, $persistent = false, $sendinit = true) + { + throw new Exception("Can't connect to server from XMPP queue proxy."); + } + + public function disconnect() + { + throw new Exception("Can't connect to server from XMPP queue proxy."); + } + + public function process() + { + throw new Exception("Can't read stream from XMPP queue proxy."); + } + + public function processUntil($event, $timeout=-1) + { + throw new Exception("Can't read stream from XMPP queue proxy."); + } + + public function read() + { + throw new Exception("Can't read stream from XMPP queue proxy."); + } + + public function readyToProcess() + { + throw new Exception("Can't read stream from XMPP queue proxy."); + } + //@} +} + diff --git a/lib/queuehandler.php b/lib/queuehandler.php index 613be6e33..2909cd83b 100644 --- a/lib/queuehandler.php +++ b/lib/queuehandler.php @@ -22,51 +22,20 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } /** * Base class for queue handlers. * - * As extensions of the Daemon class, each queue handler has the ability - * to launch itself in the background, at which point it'll pass control - * to the configured QueueManager class to poll for updates. + * As of 0.9, queue handlers are short-lived for items as they are + * dequeued by a QueueManager running in an IoMaster in a daemon + * such as queuedaemon.php. + * + * Extensions requiring long-running maintenance or polling should + * register an IoManager. * * Subclasses must override at least the following methods: * - transport - * - handle_notice + * - handle */ -#class QueueHandler extends Daemon class QueueHandler { -# function __construct($id=null, $daemonize=true) -# { -# parent::__construct($daemonize); -# -# if ($id) { -# $this->set_id($id); -# } -# } - - /** - * How many seconds a polling-based queue manager should wait between - * checks for new items to handle. - * - * Defaults to 60 seconds; override to speed up or slow down. - * - * @fixme not really compatible with global queue manager - * @return int timeout in seconds - */ -# function timeout() -# { -# return 60; -# } - -# function class_name() -# { -# return ucfirst($this->transport()) . 'Handler'; -# } - -# function name() -# { -# return strtolower($this->class_name().'.'.$this->get_id()); -# } - /** * Return transport keyword which identifies items this queue handler * services; must be defined for all subclasses. @@ -83,61 +52,17 @@ class QueueHandler /** * Here's the meat of your queue handler -- you're handed a Notice - * object, which you may do as you will with. + * or other object, which you may do as you will with. * * If this function indicates failure, a warning will be logged * and the item is placed back in the queue to be re-run. * - * @param Notice $notice - * @return boolean true on success, false on failure - */ - function handle_notice($notice) - { - return true; - } - - /** - * Setup and start of run loop for this queue handler as a daemon. - * Most of the heavy lifting is passed on to the QueueManager's service() - * method, which passes control back to our handle_notice() method for - * each notice that comes in on the queue. - * - * Most of the time this won't need to be overridden in a subclass. - * + * @param mixed $object * @return boolean true on success, false on failure */ - function run() + function handle($object) { - if (!$this->start()) { - $this->log(LOG_WARNING, 'failed to start'); - return false; - } - - $this->log(LOG_INFO, 'checking for queued notices'); - - $queue = $this->transport(); - $timeout = $this->timeout(); - - $qm = QueueManager::get(); - - $qm->service($queue, $this); - - $this->log(LOG_INFO, 'finished servicing the queue'); - - if (!$this->finish()) { - $this->log(LOG_WARNING, 'failed to clean up'); - return false; - } - - $this->log(LOG_INFO, 'terminating normally'); - return true; } - - - function log($level, $msg) - { - common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg); - } } diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 291174d3c..4eb39bfa8 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -39,6 +39,10 @@ abstract class QueueManager extends IoManager { static $qm = null; + public $master = null; + public $handlers = array(); + public $groups = array(); + /** * Factory function to pull the appropriate QueueManager object * for this site's configuration. It can then be used to queue @@ -109,6 +113,64 @@ abstract class QueueManager extends IoManager */ abstract function enqueue($object, $queue); + /** + * Build a representation for an object for logging + * @param mixed + * @return string + */ + function logrep($object) { + if (is_object($object)) { + $class = get_class($object); + if (isset($object->id)) { + return "$class $object->id"; + } + return $class; + } + if (is_string($object)) { + $len = strlen($object); + $fragment = mb_substr($object, 0, 32); + if (mb_strlen($object) > 32) { + $fragment .= '...'; + } + return "string '$fragment' ($len bytes)"; + } + return strval($object); + } + + /** + * Encode an object for queued storage. + * Next gen may use serialization. + * + * @param mixed $object + * @return string + */ + protected function encode($object) + { + if ($object instanceof Notice) { + return $object->id; + } else if (is_string($object)) { + return $object; + } else { + throw new ServerException("Can't queue this type", 500); + } + } + + /** + * Decode an object from queued storage. + * Accepts back-compat notice reference entries and strings for now. + * + * @param string + * @return mixed + */ + protected function decode($frame) + { + if (is_numeric($frame)) { + return Notice::staticGet(intval($frame)); + } else { + return $frame; + } + } + /** * Instantiate the appropriate QueueHandler class for the given queue. * @@ -131,13 +193,15 @@ abstract class QueueManager extends IoManager } /** - * Get a list of all registered queue transport names. + * Get a list of registered queue transport names to be used + * for this daemon. * * @return array of strings */ function getQueues() { - return array_keys($this->handlers); + $group = $this->activeGroup(); + return array_keys($this->groups[$group]); } /** @@ -148,33 +212,29 @@ abstract class QueueManager extends IoManager */ function initialize() { + // @fixme we'll want to be able to listen to particular queues... if (Event::handle('StartInitializeQueueManager', array($this))) { - if (!defined('XMPP_ONLY_FLAG')) { // hack! - $this->connect('plugin', 'PluginQueueHandler'); - $this->connect('omb', 'OmbQueueHandler'); - $this->connect('ping', 'PingQueueHandler'); - if (common_config('sms', 'enabled')) { - $this->connect('sms', 'SmsQueueHandler'); - } + $this->connect('plugin', 'PluginQueueHandler'); + $this->connect('omb', 'OmbQueueHandler'); + $this->connect('ping', 'PingQueueHandler'); + if (common_config('sms', 'enabled')) { + $this->connect('sms', 'SmsQueueHandler'); } // XMPP output handlers... - if (common_config('xmpp', 'enabled') && !defined('XMPP_EMERGENCY_FLAG')) { - $this->connect('jabber', 'JabberQueueHandler'); - $this->connect('public', 'PublicQueueHandler'); - - // @fixme this should move up a level or should get an actual queue - $this->connect('confirm', 'XmppConfirmHandler'); - } + $this->connect('jabber', 'JabberQueueHandler'); + $this->connect('public', 'PublicQueueHandler'); + + // @fixme this should get an actual queue + //$this->connect('confirm', 'XmppConfirmHandler'); + + // For compat with old plugins not registering their own handlers. + $this->connect('plugin', 'PluginQueueHandler'); + + $this->connect('xmppout', 'XmppOutQueueHandler', 'xmppdaemon'); - if (!defined('XMPP_ONLY_FLAG')) { // hack! - // For compat with old plugins not registering their own handlers. - $this->connect('plugin', 'PluginQueueHandler'); - } - } - if (!defined('XMPP_ONLY_FLAG')) { // hack! - Event::handle('EndInitializeQueueManager', array($this)); } + Event::handle('EndInitializeQueueManager', array($this)); } /** @@ -183,10 +243,27 @@ abstract class QueueManager extends IoManager * * @param string $transport * @param string $class + * @param string $group */ - public function connect($transport, $class) + public function connect($transport, $class, $group='queuedaemon') { $this->handlers[$transport] = $class; + $this->groups[$group][$transport] = $class; + } + + /** + * @return string queue group to use for this request + */ + function activeGroup() + { + $group = 'queuedaemon'; + if ($this->master) { + // hack hack + if ($this->master instanceof XmppMaster) { + return 'xmppdaemon'; + } + } + return $group; } /** diff --git a/lib/smsqueuehandler.php b/lib/smsqueuehandler.php index 48a96409d..6085d2b4a 100644 --- a/lib/smsqueuehandler.php +++ b/lib/smsqueuehandler.php @@ -31,7 +31,7 @@ class SmsQueueHandler extends QueueHandler return 'sms'; } - function handle_notice($notice) + function handle($notice) { require_once(INSTALLDIR.'/lib/mail.php'); return mail_broadcast_notice_sms($notice); diff --git a/lib/spawningdaemon.php b/lib/spawningdaemon.php new file mode 100644 index 000000000..8baefe88e --- /dev/null +++ b/lib/spawningdaemon.php @@ -0,0 +1,159 @@ +. + */ + +/** + * Base class for daemon that can launch one or more processing threads, + * respawning them if they exit. + * + * This is mainly intended for indefinite workloads such as monitoring + * a queue or maintaining an IM channel. + * + * Child classes should implement the + * + * We can then pass individual items through the QueueHandler subclasses + * they belong to. We additionally can handle queues for multiple sites. + * + * @package QueueHandler + * @author Brion Vibber + */ +abstract class SpawningDaemon extends Daemon +{ + protected $threads=1; + + function __construct($id=null, $daemonize=true, $threads=1) + { + parent::__construct($daemonize); + + if ($id) { + $this->set_id($id); + } + $this->threads = $threads; + } + + /** + * Perform some actual work! + * + * @return boolean true on success, false on failure + */ + public abstract function runThread(); + + /** + * Spawn one or more background processes and let them start running. + * Each individual process will execute whatever's in the runThread() + * method, which should be overridden. + * + * Child processes will be automatically respawned when they exit. + * + * @todo possibly allow for not respawning on "normal" exits... + * though ParallelizingDaemon is probably better for workloads + * that have forseeable endpoints. + */ + function run() + { + $children = array(); + for ($i = 1; $i <= $this->threads; $i++) { + $pid = pcntl_fork(); + if ($pid < 0) { + $this->log(LOG_ERROR, "Couldn't fork for thread $i; aborting\n"); + exit(1); + } else if ($pid == 0) { + $this->initAndRunChild($i); + } else { + $this->log(LOG_INFO, "Spawned thread $i as pid $pid"); + $children[$i] = $pid; + } + } + + $this->log(LOG_INFO, "Waiting for children to complete."); + while (count($children) > 0) { + $status = null; + $pid = pcntl_wait($status); + if ($pid > 0) { + $i = array_search($pid, $children); + if ($i === false) { + $this->log(LOG_ERR, "Unrecognized child pid $pid exited!"); + continue; + } + unset($children[$i]); + $this->log(LOG_INFO, "Thread $i pid $pid exited."); + + $pid = pcntl_fork(); + if ($pid < 0) { + $this->log(LOG_ERROR, "Couldn't fork to respawn thread $i; aborting thread.\n"); + } else if ($pid == 0) { + $this->initAndRunChild($i); + } else { + $this->log(LOG_INFO, "Respawned thread $i as pid $pid"); + $children[$i] = $pid; + } + } + } + $this->log(LOG_INFO, "All child processes complete."); + return true; + } + + /** + * Initialize things for a fresh thread, call runThread(), and + * exit at completion with appropriate return value. + */ + protected function initAndRunChild($thread) + { + $this->set_id($this->get_id() . "." . $thread); + $this->resetDb(); + $ok = $this->runThread(); + exit($ok ? 0 : 1); + } + + /** + * Reconnect to the database for each child process, + * or they'll get very confused trying to use the + * same socket. + */ + protected function resetDb() + { + // @fixme do we need to explicitly open the db too + // or is this implied? + global $_DB_DATAOBJECT; + unset($_DB_DATAOBJECT['CONNECTIONS']); + + // Reconnect main memcached, or threads will stomp on + // each other and corrupt their requests. + $cache = common_memcache(); + if ($cache) { + $cache->reconnect(); + } + + // Also reconnect memcached for status_network table. + if (!empty(Status_network::$cache)) { + Status_network::$cache->close(); + Status_network::$cache = null; + } + } + + function log($level, $msg) + { + common_log($level, get_class($this) . ' ('. $this->get_id() .'): '.$msg); + } + + function name() + { + return strtolower(get_class($this).'.'.$this->get_id()); + } +} + diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index 00590fdb6..f057bd9e4 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -39,7 +39,6 @@ class StompQueueManager extends QueueManager var $base = null; var $con = null; - protected $master = null; protected $sites = array(); function __construct() @@ -104,11 +103,12 @@ class StompQueueManager extends QueueManager */ function getQueues() { + $group = $this->activeGroup(); $site = common_config('site', 'server'); - if (empty($this->handlers[$site])) { + if (empty($this->groups[$site][$group])) { return array(); } else { - return array_keys($this->handlers[$site]); + return array_keys($this->groups[$site][$group]); } } @@ -118,10 +118,12 @@ class StompQueueManager extends QueueManager * * @param string $transport * @param string $class + * @param string $group */ - public function connect($transport, $class) + public function connect($transport, $class, $group='queuedaemon') { $this->handlers[common_config('site', 'server')][$transport] = $class; + $this->groups[common_config('site', 'server')][$group][$transport] = $class; } /** @@ -130,23 +132,23 @@ class StompQueueManager extends QueueManager */ public function enqueue($object, $queue) { - $notice = $object; + $msg = $this->encode($object); + $rep = $this->logrep($object); $this->_connect(); // XXX: serialize and send entire notice $result = $this->con->send($this->queueName($queue), - $notice->id, // BODY of the message - array ('created' => $notice->created)); + $msg, // BODY of the message + array ('created' => common_sql_now())); if (!$result) { - common_log(LOG_ERR, 'Error sending to '.$queue.' queue'); + common_log(LOG_ERR, "Error sending $rep to $queue queue"); return false; } - common_log(LOG_DEBUG, 'complete remote queueing notice ID = ' - . $notice->id . ' for ' . $queue); + common_log(LOG_DEBUG, "complete remote queueing $rep for $queue"); $this->stats('enqueued', $queue); } @@ -174,7 +176,7 @@ class StompQueueManager extends QueueManager $ok = true; $frames = $this->con->readFrames(); foreach ($frames as $frame) { - $ok = $ok && $this->_handleNotice($frame); + $ok = $ok && $this->_handleItem($frame); } return $ok; } @@ -265,7 +267,7 @@ class StompQueueManager extends QueueManager } /** - * Handle and acknowledge a notice event that's come in through a queue. + * Handle and acknowledge an event that's come in through a queue. * * If the queue handler reports failure, the message is requeued for later. * Missing notices or handler classes will drop the message. @@ -276,7 +278,7 @@ class StompQueueManager extends QueueManager * @param StompFrame $frame * @return bool */ - protected function _handleNotice($frame) + protected function _handleItem($frame) { list($site, $queue) = $this->parseDestination($frame->headers['destination']); if ($site != common_config('site', 'server')) { @@ -284,15 +286,23 @@ class StompQueueManager extends QueueManager StatusNet::init($site); } - $id = intval($frame->body); - $info = "notice $id posted at {$frame->headers['created']} in queue $queue"; + if (is_numeric($frame->body)) { + $id = intval($frame->body); + $info = "notice $id posted at {$frame->headers['created']} in queue $queue"; - $notice = Notice::staticGet('id', $id); - if (empty($notice)) { - $this->_log(LOG_WARNING, "Skipping missing $info"); - $this->con->ack($frame); - $this->stats('badnotice', $queue); - return false; + $notice = Notice::staticGet('id', $id); + if (empty($notice)) { + $this->_log(LOG_WARNING, "Skipping missing $info"); + $this->con->ack($frame); + $this->stats('badnotice', $queue); + return false; + } + + $item = $notice; + } else { + // @fixme should we serialize, or json, or what here? + $info = "string posted at {$frame->headers['created']} in queue $queue"; + $item = $frame->body; } $handler = $this->getHandler($queue); @@ -303,7 +313,7 @@ class StompQueueManager extends QueueManager return false; } - $ok = $handler->handle_notice($notice); + $ok = $handler->handle($item); if (!$ok) { $this->_log(LOG_WARNING, "Failed handling $info"); @@ -311,7 +321,7 @@ class StompQueueManager extends QueueManager // this kind of queue management ourselves; // if we don't ack, it should resend... $this->con->ack($frame); - $this->enqueue($notice, $queue); + $this->enqueue($item, $queue); $this->stats('requeued', $queue); return false; } diff --git a/lib/util.php b/lib/util.php index ef8a5d1f0..fb3b8be87 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1130,7 +1130,8 @@ function common_request_id() $pid = getmypid(); $server = common_config('site', 'server'); if (php_sapi_name() == 'cli') { - return "$server:$pid"; + $script = basename($_SERVER['PHP_SELF']); + return "$server:$script:$pid"; } else { static $req_id = null; if (!isset($req_id)) { diff --git a/lib/xmppconfirmmanager.php b/lib/xmppconfirmmanager.php deleted file mode 100644 index ee4e294fd..000000000 --- a/lib/xmppconfirmmanager.php +++ /dev/null @@ -1,168 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -/** - * Event handler for pushing new confirmations to Jabber users. - * @fixme recommend redoing this on a queue-trigger model - * @fixme expiration of old items got dropped in the past, put it back? - */ -class XmppConfirmManager extends IoManager -{ - - /** - * @return mixed XmppConfirmManager, or false if unneeded - */ - public static function get() - { - if (common_config('xmpp', 'enabled')) { - $site = common_config('site', 'server'); - return new XmppConfirmManager(); - } else { - return false; - } - } - - /** - * Tell the i/o master we need one instance for each supporting site - * being handled in this process. - */ - public static function multiSite() - { - return IoManager::INSTANCE_PER_SITE; - } - - function __construct() - { - $this->site = common_config('site', 'server'); - } - - /** - * 10 seconds? Really? That seems a bit frequent. - */ - function pollInterval() - { - return 10; - } - - /** - * Ping! - * @return boolean true if we found something - */ - function poll() - { - $this->switchSite(); - $confirm = $this->next_confirm(); - if ($confirm) { - $this->handle_confirm($confirm); - return true; - } else { - return false; - } - } - - protected function handle_confirm($confirm) - { - require_once INSTALLDIR . '/lib/jabber.php'; - - common_log(LOG_INFO, 'Sending confirmation for ' . $confirm->address); - $user = User::staticGet($confirm->user_id); - if (!$user) { - common_log(LOG_WARNING, 'Confirmation for unknown user ' . $confirm->user_id); - return; - } - $success = jabber_confirm_address($confirm->code, - $user->nickname, - $confirm->address); - if (!$success) { - common_log(LOG_ERR, 'Confirmation failed for ' . $confirm->address); - # Just let the claim age out; hopefully things work then - return; - } else { - common_log(LOG_INFO, 'Confirmation sent for ' . $confirm->address); - # Mark confirmation sent; need a dupe so we don't have the WHERE clause - $dupe = Confirm_address::staticGet('code', $confirm->code); - if (!$dupe) { - common_log(LOG_WARNING, 'Could not refetch confirm', __FILE__); - return; - } - $orig = clone($dupe); - $dupe->sent = $dupe->claimed; - $result = $dupe->update($orig); - if (!$result) { - common_log_db_error($dupe, 'UPDATE', __FILE__); - # Just let the claim age out; hopefully things work then - return; - } - } - return true; - } - - protected function next_confirm() - { - $confirm = new Confirm_address(); - $confirm->whereAdd('claimed IS null'); - $confirm->whereAdd('sent IS null'); - # XXX: eventually we could do other confirmations in the queue, too - $confirm->address_type = 'jabber'; - $confirm->orderBy('modified DESC'); - $confirm->limit(1); - if ($confirm->find(true)) { - common_log(LOG_INFO, 'Claiming confirmation for ' . $confirm->address); - # working around some weird DB_DataObject behaviour - $confirm->whereAdd(''); # clears where stuff - $original = clone($confirm); - $confirm->claimed = common_sql_now(); - $result = $confirm->update($original); - if ($result) { - common_log(LOG_INFO, 'Succeeded in claim! '. $result); - return $confirm; - } else { - common_log(LOG_INFO, 'Failed in claim!'); - return false; - } - } - return null; - } - - protected function clear_old_confirm_claims() - { - $confirm = new Confirm(); - $confirm->claimed = null; - $confirm->whereAdd('now() - claimed > '.CLAIM_TIMEOUT); - $confirm->update(DB_DATAOBJECT_WHEREADD_ONLY); - $confirm->free(); - unset($confirm); - } - - /** - * Make sure we're on the right site configuration - */ - protected function switchSite() - { - if ($this->site != common_config('site', 'server')) { - common_log(LOG_DEBUG, __METHOD__ . ": switching to site $this->site"); - $this->stats('switch'); - StatusNet::init($this->site); - } - } -} diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php index dfff63a30..299175dd7 100644 --- a/lib/xmppmanager.php +++ b/lib/xmppmanager.php @@ -70,6 +70,7 @@ class XmppManager extends IoManager function __construct() { $this->site = common_config('site', 'server'); + $this->resource = common_config('xmpp', 'resource') . 'daemon'; } /** @@ -86,15 +87,19 @@ class XmppManager extends IoManager # Low priority; we don't want to receive messages common_log(LOG_INFO, "INITIALIZE"); - $this->conn = jabber_connect($this->resource()); + $this->conn = jabber_connect($this->resource); if (empty($this->conn)) { common_log(LOG_ERR, "Couldn't connect to server."); return false; } - $this->conn->addEventHandler('message', 'forward_message', $this); + $this->log(LOG_DEBUG, "Initializing stanza handlers."); + + $this->conn->addEventHandler('message', 'handle_message', $this); + $this->conn->addEventHandler('presence', 'handle_presence', $this); $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this); + $this->conn->setReconnectTimeout(600); jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', -1); @@ -175,12 +180,37 @@ class XmppManager extends IoManager } } + /** + * For queue handlers to pass us a message to push out, + * if we're active. + * + * @fixme should this be blocking etc? + * + * @param string $msg XML stanza to send + * @return boolean success + */ + public function send($msg) + { + if ($this->conn && !$this->conn->isDisconnected()) { + $bytes = $this->conn->send($msg); + if ($bytes > 0) { + $this->conn->processTime(0); + return true; + } else { + return false; + } + } else { + // Can't send right now... + return false; + } + } + /** * Send a keepalive ping to the XMPP server. */ protected function sendPing() { - $jid = jabber_daemon_address().'/'.$this->resource(); + $jid = jabber_daemon_address().'/'.$this->resource; $server = common_config('xmpp', 'server'); if (!isset($this->pingid)) { @@ -206,61 +236,239 @@ class XmppManager extends IoManager $this->conn->presence(null, 'available', null, 'available', -1); } + + function get_user($from) + { + $user = User::staticGet('jabber', jabber_normalize_jid($from)); + return $user; + } + /** - * Callback for Jabber message event. - * - * This connection handles output; if we get a message straight to us, - * forward it on to our XmppDaemon listener for processing. - * - * @param $pl + * XMPP callback for handling message input... + * @param array $pl XMPP payload */ - function forward_message(&$pl) + function handle_message(&$pl) { + $from = jabber_normalize_jid($pl['from']); + if ($pl['type'] != 'chat') { - common_log(LOG_DEBUG, 'Ignoring message of type ' . $pl['type'] . ' from ' . $pl['from']); + $this->log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from."); return; } - $listener = $this->listener(); - if (strtolower($listener) == strtolower($pl['from'])) { - common_log(LOG_WARNING, 'Ignoring loop message.'); + + if (mb_strlen($pl['body']) == 0) { + $this->log(LOG_WARNING, "Ignoring message with empty body from $from."); return; } - common_log(LOG_INFO, 'Forwarding message from ' . $pl['from'] . ' to ' . $listener); - $this->conn->message($this->listener(), $pl['body'], 'chat', null, $this->ofrom($pl['from'])); + + // Forwarded from another daemon for us to handle; this shouldn't + // happen any more but we might get some legacy items. + if ($this->is_self($from)) { + $this->log(LOG_INFO, "Got forwarded notice from self ($from)."); + $from = $this->get_ofrom($pl); + $this->log(LOG_INFO, "Originally sent by $from."); + if (is_null($from) || $this->is_self($from)) { + $this->log(LOG_INFO, "Ignoring notice originally sent by $from."); + return; + } + } + + $user = $this->get_user($from); + + // For common_current_user to work + global $_cur; + $_cur = $user; + + if (!$user) { + $this->from_site($from, 'Unknown user; go to ' . + common_local_url('imsettings') . + ' to add your address to your account'); + $this->log(LOG_WARNING, 'Message from unknown user ' . $from); + return; + } + if ($this->handle_command($user, $pl['body'])) { + $this->log(LOG_INFO, "Command message by $from handled."); + return; + } else if ($this->is_autoreply($pl['body'])) { + $this->log(LOG_INFO, 'Ignoring auto reply from ' . $from); + return; + } else if ($this->is_otr($pl['body'])) { + $this->log(LOG_INFO, 'Ignoring OTR from ' . $from); + return; + } else { + + $this->log(LOG_INFO, 'Posting a notice from ' . $user->nickname); + + $this->add_notice($user, $pl); + } + + $user->free(); + unset($user); + unset($_cur); + + unset($pl['xml']); + $pl['xml'] = null; + + $pl = null; + unset($pl); } - /** - * Build an block with an ofrom entry for forwarded messages - * - * @param string $from Jabber ID of original sender - * @return string XML fragment - */ - protected function ofrom($from) + + function is_self($from) { - $address = "\n"; - $address .= "
\n"; - $address .= "\n"; - return $address; + return preg_match('/^'.strtolower(jabber_daemon_address()).'/', strtolower($from)); } - /** - * Build the complete JID of the XmppDaemon process which - * handles primary XMPP input for this site. - * - * @return string Jabber ID - */ - protected function listener() + function get_ofrom($pl) + { + $xml = $pl['xml']; + $addresses = $xml->sub('addresses'); + if (!$addresses) { + $this->log(LOG_WARNING, 'Forwarded message without addresses'); + return null; + } + $address = $addresses->sub('address'); + if (!$address) { + $this->log(LOG_WARNING, 'Forwarded message without address'); + return null; + } + if (!array_key_exists('type', $address->attrs)) { + $this->log(LOG_WARNING, 'No type for forwarded message'); + return null; + } + $type = $address->attrs['type']; + if ($type != 'ofrom') { + $this->log(LOG_WARNING, 'Type of forwarded message is not ofrom'); + return null; + } + if (!array_key_exists('jid', $address->attrs)) { + $this->log(LOG_WARNING, 'No jid for forwarded message'); + return null; + } + $jid = $address->attrs['jid']; + if (!$jid) { + $this->log(LOG_WARNING, 'Could not get jid from address'); + return null; + } + $this->log(LOG_DEBUG, 'Got message forwarded from jid ' . $jid); + return $jid; + } + + function is_autoreply($txt) + { + if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) { + return true; + } else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) { + return true; + } else { + return false; + } + } + + function is_otr($txt) + { + if (preg_match('/^\?OTR/', $txt)) { + return true; + } else { + return false; + } + } + + function from_site($address, $msg) + { + $text = '['.common_config('site', 'name') . '] ' . $msg; + jabber_send_message($address, $text); + } + + function handle_command($user, $body) { - if (common_config('xmpp', 'listener')) { - return common_config('xmpp', 'listener'); + $inter = new CommandInterpreter(); + $cmd = $inter->handle_command($user, $body); + if ($cmd) { + $chan = new XMPPChannel($this->conn); + $cmd->execute($chan); + return true; } else { - return jabber_daemon_address() . '/' . common_config('xmpp','resource') . 'daemon'; + return false; + } + } + + function add_notice(&$user, &$pl) + { + $body = trim($pl['body']); + $content_shortened = common_shorten_links($body); + if (Notice::contentTooLong($content_shortened)) { + $from = jabber_normalize_jid($pl['from']); + $this->from_site($from, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d.'), + Notice::maxContent(), + mb_strlen($content_shortened))); + return; + } + + try { + $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp'); + } catch (Exception $e) { + $this->log(LOG_ERR, $e->getMessage()); + $this->from_site($user->jabber, $e->getMessage()); + return; } + + common_broadcast_notice($notice); + $this->log(LOG_INFO, + 'Added notice ' . $notice->id . ' from user ' . $user->nickname); + $notice->free(); + unset($notice); + } + + function handle_presence(&$pl) + { + $from = jabber_normalize_jid($pl['from']); + switch ($pl['type']) { + case 'subscribe': + # We let anyone subscribe + $this->subscribed($from); + $this->log(LOG_INFO, + 'Accepted subscription from ' . $from); + break; + case 'subscribed': + case 'unsubscribed': + case 'unsubscribe': + $this->log(LOG_INFO, + 'Ignoring "' . $pl['type'] . '" from ' . $from); + break; + default: + if (!$pl['type']) { + $user = User::staticGet('jabber', $from); + if (!$user) { + $this->log(LOG_WARNING, 'Presence from unknown user ' . $from); + return; + } + if ($user->updatefrompresence) { + $this->log(LOG_INFO, 'Updating ' . $user->nickname . + ' status from presence.'); + $this->add_notice($user, $pl); + } + $user->free(); + unset($user); + } + break; + } + unset($pl['xml']); + $pl['xml'] = null; + + $pl = null; + unset($pl); + } + + function log($level, $msg) + { + $text = 'XMPPDaemon('.$this->resource.'): '.$msg; + common_log($level, $text); } - protected function resource() + function subscribed($to) { - return 'queue' . posix_getpid(); // @fixme PIDs won't be host-unique + jabber_special_presence('subscribed', $to); } /** diff --git a/lib/xmppoutqueuehandler.php b/lib/xmppoutqueuehandler.php new file mode 100644 index 000000000..2afa260f1 --- /dev/null +++ b/lib/xmppoutqueuehandler.php @@ -0,0 +1,55 @@ +. + */ + +/** + * Queue handler for pre-processed outgoing XMPP messages. + * Formatted XML stanzas will have been pushed into the queue + * via the Queued_XMPP connection proxy, probably from some + * other queue processor. + * + * Here, the XML stanzas are simply pulled out of the queue and + * pushed out over the wire; an XmppManager is needed to set up + * and maintain the actual server connection. + * + * This queue will be run via XmppDaemon rather than QueueDaemon. + * + * @author Brion Vibber + */ +class XmppOutQueueHandler extends QueueHandler +{ + function transport() { + return 'xmppout'; + } + + /** + * Take a previously-queued XMPP stanza and send it out ot the server. + * @param string $msg + * @return boolean true on success + */ + function handle($msg) + { + assert(is_string($msg)); + + $xmpp = XmppManager::get(); + $ok = $xmpp->send($msg); + + return $ok; + } +} + diff --git a/plugins/Enjit/enjitqueuehandler.php b/plugins/Enjit/enjitqueuehandler.php index f0e706b92..14085cc5e 100644 --- a/plugins/Enjit/enjitqueuehandler.php +++ b/plugins/Enjit/enjitqueuehandler.php @@ -32,14 +32,7 @@ class EnjitQueueHandler extends QueueHandler return 'enjit'; } - function start() - { - $this->log(LOG_INFO, "Starting EnjitQueueHandler"); - $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl')); - return true; - } - - function handle_notice($notice) + function handle($notice) { $profile = Profile::staticGet($notice->profile_id); diff --git a/plugins/Facebook/facebookqueuehandler.php b/plugins/Facebook/facebookqueuehandler.php index 1778690e5..524af7bc4 100644 --- a/plugins/Facebook/facebookqueuehandler.php +++ b/plugins/Facebook/facebookqueuehandler.php @@ -28,7 +28,7 @@ class FacebookQueueHandler extends QueueHandler return 'facebook'; } - function handle_notice($notice) + function handle($notice) { if ($this->_isLocal($notice)) { return facebookBroadcastNotice($notice); diff --git a/plugins/RSSCloud/RSSCloudPlugin.php b/plugins/RSSCloud/RSSCloudPlugin.php index 2de162628..9f444c8bb 100644 --- a/plugins/RSSCloud/RSSCloudPlugin.php +++ b/plugins/RSSCloud/RSSCloudPlugin.php @@ -138,6 +138,9 @@ class RSSCloudPlugin extends Plugin case 'RSSCloudNotifier': include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudNotifier.php'; return false; + case 'RSSCloudQueueHandler': + include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudQueueHandler.php'; + return false; case 'RSSCloudRequestNotifyAction': case 'LoggingAggregatorAction': include_once INSTALLDIR . '/plugins/RSSCloud/' . @@ -193,32 +196,6 @@ class RSSCloudPlugin extends Plugin return true; } - /** - * broadcast the message when not using queuehandler - * - * @param Notice &$notice the notice - * @param array $queue destination queue - * - * @return boolean hook return - */ - - function onUnqueueHandleNotice(&$notice, $queue) - { - if (($queue == 'rsscloud') && ($this->_isLocal($notice))) { - - common_debug('broadcasting rssCloud bound notice ' . $notice->id); - - $profile = $notice->getProfile(); - - $notifier = new RSSCloudNotifier(); - $notifier->notify($profile); - - return false; - } - - return true; - } - /** * Determine whether the notice was locally created * @@ -261,19 +238,15 @@ class RSSCloudPlugin extends Plugin } /** - * Add RSSCloudQueueHandler to the list of valid daemons to - * start + * Register RSSCloud notice queue handler * - * @param array $daemons the list of daemons to run + * @param QueueManager $manager * * @return boolean hook return - * */ - - function onGetValidDaemons($daemons) + function onEndInitializeQueueManager($manager) { - array_push($daemons, INSTALLDIR . - '/plugins/RSSCloud/RSSCloudQueueHandler.php'); + $manager->connect('rsscloud', 'RSSCloudQueueHandler'); return true; } diff --git a/plugins/RSSCloud/RSSCloudQueueHandler.php b/plugins/RSSCloud/RSSCloudQueueHandler.php old mode 100755 new mode 100644 index 693dd27c1..295c26189 --- a/plugins/RSSCloud/RSSCloudQueueHandler.php +++ b/plugins/RSSCloud/RSSCloudQueueHandler.php @@ -1,4 +1,3 @@ -#!/usr/bin/env php . */ -define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); - -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = <<log(LOG_INFO, "INITIALIZE"); - $this->notifier = new RSSCloudNotifier(); - return true; - } - - function handle_notice($notice) + function handle($notice) { $profile = $notice->getProfile(); - return $this->notifier->notify($profile); - } - - function finish() - { + $notifier = new RSSCloudNotifier(); + return $notifier->notify($profile); } - -} - -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; } -$handler = new RSSCloudQueueHandler($id); - -$handler->runOnce(); diff --git a/plugins/TwitterBridge/twitterqueuehandler.php b/plugins/TwitterBridge/twitterqueuehandler.php index 5089ca7b7..b5a624e83 100644 --- a/plugins/TwitterBridge/twitterqueuehandler.php +++ b/plugins/TwitterBridge/twitterqueuehandler.php @@ -28,7 +28,7 @@ class TwitterQueueHandler extends QueueHandler return 'twitter'; } - function handle_notice($notice) + function handle($notice) { return broadcast_twitter($notice); } diff --git a/scripts/handlequeued.php b/scripts/handlequeued.php index 9031437aa..815884969 100755 --- a/scripts/handlequeued.php +++ b/scripts/handlequeued.php @@ -50,7 +50,7 @@ if (empty($notice)) { exit(1); } -if (!$handler->handle_notice($notice)) { +if (!$handler->handle($notice)) { print "Failed to handle notice id $noticeId on queue '$queue'.\n"; exit(1); } diff --git a/scripts/queuedaemon.php b/scripts/queuedaemon.php index 162f617e0..a9cfda6d7 100755 --- a/scripts/queuedaemon.php +++ b/scripts/queuedaemon.php @@ -29,6 +29,8 @@ $longoptions = array('id=', 'foreground', 'all', 'threads=', 'skip-xmpp', 'xmpp- * * Recognizes Linux and Mac OS X; others will return default of 1. * + * @fixme move this to SpawningDaemon, but to get the default val for help + * text we seem to need it before loading infrastructure * @return intval */ function getProcessorCount() @@ -83,143 +85,29 @@ define('CLAIM_TIMEOUT', 1200); * We can then pass individual items through the QueueHandler subclasses * they belong to. */ -class QueueDaemon extends Daemon +class QueueDaemon extends SpawningDaemon { - protected $allsites; - protected $threads=1; + protected $allsites = false; function __construct($id=null, $daemonize=true, $threads=1, $allsites=false) { - parent::__construct($daemonize); - - if ($id) { - $this->set_id($id); - } + parent::__construct($id, $daemonize, $threads); $this->all = $allsites; - $this->threads = $threads; - } - - /** - * How many seconds a polling-based queue manager should wait between - * checks for new items to handle. - * - * Defaults to 60 seconds; override to speed up or slow down. - * - * @return int timeout in seconds - */ - function timeout() - { - return 60; - } - - function name() - { - return strtolower(get_class($this).'.'.$this->get_id()); - } - - function run() - { - if ($this->threads > 1) { - return $this->runThreads(); - } else { - return $this->runLoop(); - } - } - - function runThreads() - { - $children = array(); - for ($i = 1; $i <= $this->threads; $i++) { - $pid = pcntl_fork(); - if ($pid < 0) { - print "Couldn't fork for thread $i; aborting\n"; - exit(1); - } else if ($pid == 0) { - $this->runChild($i); - exit(0); - } else { - $this->log(LOG_INFO, "Spawned thread $i as pid $pid"); - $children[$i] = $pid; - } - } - - $this->log(LOG_INFO, "Waiting for children to complete."); - while (count($children) > 0) { - $status = null; - $pid = pcntl_wait($status); - if ($pid > 0) { - $i = array_search($pid, $children); - if ($i === false) { - $this->log(LOG_ERR, "Unrecognized child pid $pid exited!"); - continue; - } - unset($children[$i]); - $this->log(LOG_INFO, "Thread $i pid $pid exited."); - - $pid = pcntl_fork(); - if ($pid < 0) { - print "Couldn't fork to respawn thread $i; aborting thread.\n"; - } else if ($pid == 0) { - $this->runChild($i); - exit(0); - } else { - $this->log(LOG_INFO, "Respawned thread $i as pid $pid"); - $children[$i] = $pid; - } - } - } - $this->log(LOG_INFO, "All child processes complete."); - return true; - } - - function runChild($thread) - { - $this->set_id($this->get_id() . "." . $thread); - $this->resetDb(); - $this->runLoop(); - } - - /** - * Reconnect to the database for each child process, - * or they'll get very confused trying to use the - * same socket. - */ - function resetDb() - { - // @fixme do we need to explicitly open the db too - // or is this implied? - global $_DB_DATAOBJECT; - unset($_DB_DATAOBJECT['CONNECTIONS']); - - // Reconnect main memcached, or threads will stomp on - // each other and corrupt their requests. - $cache = common_memcache(); - if ($cache) { - $cache->reconnect(); - } - - // Also reconnect memcached for status_network table. - if (!empty(Status_network::$cache)) { - Status_network::$cache->close(); - Status_network::$cache = null; - } } /** * Setup and start of run loop for this queue handler as a daemon. * Most of the heavy lifting is passed on to the QueueManager's service() - * method, which passes control on to the QueueHandler's handle_notice() - * method for each notice that comes in on the queue. - * - * Most of the time this won't need to be overridden in a subclass. + * method, which passes control on to the QueueHandler's handle() + * method for each item that comes in on the queue. * * @return boolean true on success, false on failure */ - function runLoop() + function runThread() { $this->log(LOG_INFO, 'checking for queued notices'); - $master = new IoMaster($this->get_id()); + $master = new QueueMaster($this->get_id()); $master->init($this->all); $master->service(); @@ -229,10 +117,25 @@ class QueueDaemon extends Daemon return true; } +} - function log($level, $msg) +class QueueMaster extends IoMaster +{ + /** + * Initialize IoManagers for the currently configured site + * which are appropriate to this instance. + */ + function initManagers() { - common_log($level, get_class($this) . ' ('. $this->get_id() .'): '.$msg); + $classes = array(); + if (Event::handle('StartQueueDaemonIoManagers', array(&$classes))) { + $classes[] = 'QueueManager'; + } + Event::handle('EndQueueDaemonIoManagers', array(&$classes)); + + foreach ($classes as $class) { + $this->instantiate($class); + } } } diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php index cef9c4bd0..fd7cf055b 100755 --- a/scripts/xmppdaemon.php +++ b/scripts/xmppdaemon.php @@ -33,347 +33,46 @@ END_OF_XMPP_HELP; require_once INSTALLDIR.'/scripts/commandline.inc'; -require_once INSTALLDIR . '/lib/common.php'; require_once INSTALLDIR . '/lib/jabber.php'; -require_once INSTALLDIR . '/lib/daemon.php'; -# This is kind of clunky; we create a class to call the global functions -# in jabber.php, which create a new XMPP class. A more elegant (?) solution -# might be to use make this a subclass of XMPP. - -class XMPPDaemon extends Daemon +class XMPPDaemon extends SpawningDaemon { - function __construct($resource=null, $daemonize=true) - { - parent::__construct($daemonize); - - static $attrs = array('server', 'port', 'user', 'password', 'host'); - - foreach ($attrs as $attr) - { - $this->$attr = common_config('xmpp', $attr); - } - - if ($resource) { - $this->resource = $resource . 'daemon'; - } else { - $this->resource = common_config('xmpp', 'resource') . 'daemon'; - } - - $this->jid = $this->user.'@'.$this->server.'/'.$this->resource; - - $this->log(LOG_INFO, "INITIALIZE XMPPDaemon {$this->jid}"); - } - - function connect() - { - $connect_to = ($this->host) ? $this->host : $this->server; - - $this->log(LOG_INFO, "Connecting to $connect_to on port $this->port"); - - $this->conn = jabber_connect($this->resource); - - if (!$this->conn) { - return false; - } - - $this->log(LOG_INFO, "Connected"); - - $this->conn->setReconnectTimeout(600); - - $this->log(LOG_INFO, "Sending initial presence."); - - jabber_send_presence("Send me a message to post a notice", 'available', - null, 'available', 100); - - $this->log(LOG_INFO, "Done connecting."); - - return !$this->conn->isDisconnected(); - } - - function name() - { - return strtolower('xmppdaemon.'.$this->resource); - } - - function run() + function __construct($id=null, $daemonize=true, $threads=1) { - if ($this->connect()) { - - $this->log(LOG_DEBUG, "Initializing stanza handlers."); - - $this->conn->addEventHandler('message', 'handle_message', $this); - $this->conn->addEventHandler('presence', 'handle_presence', $this); - $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this); - - $this->log(LOG_DEBUG, "Beginning processing loop."); - - while ($this->conn->processTime(60)) { - $this->sendPing(); - } + if ($threads != 1) { + // This should never happen. :) + throw new Exception("XMPPDaemon can must run single-threaded"); } + parent::__construct($id, $daemonize, $threads); } - function sendPing() + function runThread() { - if (!isset($this->pingid)) { - $this->pingid = 0; - } else { - $this->pingid++; - } + common_log(LOG_INFO, 'Waiting to listen to XMPP and queues'); - $this->log(LOG_DEBUG, "Sending ping #{$this->pingid}"); + $master = new XmppMaster($this->get_id()); + $master->init(); + $master->service(); - $this->conn->send(""); - } + common_log(LOG_INFO, 'terminating normally'); - function handle_reconnect(&$pl) - { - $this->log(LOG_DEBUG, "Got reconnection callback."); - $this->conn->processUntil('session_start'); - $this->log(LOG_DEBUG, "Sending reconnection presence."); - $this->conn->presence('Send me a message to post a notice', 'available', null, 'available', 100); - unset($pl['xml']); - $pl['xml'] = null; - - $pl = null; - unset($pl); - } - - function get_user($from) - { - $user = User::staticGet('jabber', jabber_normalize_jid($from)); - return $user; + return true; } - function handle_message(&$pl) - { - $from = jabber_normalize_jid($pl['from']); - - if ($pl['type'] != 'chat') { - $this->log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from."); - return; - } - - if (mb_strlen($pl['body']) == 0) { - $this->log(LOG_WARNING, "Ignoring message with empty body from $from."); - return; - } - - # Forwarded from another daemon (probably a broadcaster) for - # us to handle - - if ($this->is_self($from)) { - $this->log(LOG_INFO, "Got forwarded notice from self ($from)."); - $from = $this->get_ofrom($pl); - $this->log(LOG_INFO, "Originally sent by $from."); - if (is_null($from) || $this->is_self($from)) { - $this->log(LOG_INFO, "Ignoring notice originally sent by $from."); - return; - } - } - - $user = $this->get_user($from); - - // For common_current_user to work - global $_cur; - $_cur = $user; - - if (!$user) { - $this->from_site($from, 'Unknown user; go to ' . - common_local_url('imsettings') . - ' to add your address to your account'); - $this->log(LOG_WARNING, 'Message from unknown user ' . $from); - return; - } - if ($this->handle_command($user, $pl['body'])) { - $this->log(LOG_INFO, "Command message by $from handled."); - return; - } else if ($this->is_autoreply($pl['body'])) { - $this->log(LOG_INFO, 'Ignoring auto reply from ' . $from); - return; - } else if ($this->is_otr($pl['body'])) { - $this->log(LOG_INFO, 'Ignoring OTR from ' . $from); - return; - } else { - - $this->log(LOG_INFO, 'Posting a notice from ' . $user->nickname); - - $this->add_notice($user, $pl); - } - - $user->free(); - unset($user); - unset($_cur); - - unset($pl['xml']); - $pl['xml'] = null; - - $pl = null; - unset($pl); - } - - function is_self($from) - { - return preg_match('/^'.strtolower(jabber_daemon_address()).'/', strtolower($from)); - } - - function get_ofrom($pl) - { - $xml = $pl['xml']; - $addresses = $xml->sub('addresses'); - if (!$addresses) { - $this->log(LOG_WARNING, 'Forwarded message without addresses'); - return null; - } - $address = $addresses->sub('address'); - if (!$address) { - $this->log(LOG_WARNING, 'Forwarded message without address'); - return null; - } - if (!array_key_exists('type', $address->attrs)) { - $this->log(LOG_WARNING, 'No type for forwarded message'); - return null; - } - $type = $address->attrs['type']; - if ($type != 'ofrom') { - $this->log(LOG_WARNING, 'Type of forwarded message is not ofrom'); - return null; - } - if (!array_key_exists('jid', $address->attrs)) { - $this->log(LOG_WARNING, 'No jid for forwarded message'); - return null; - } - $jid = $address->attrs['jid']; - if (!$jid) { - $this->log(LOG_WARNING, 'Could not get jid from address'); - return null; - } - $this->log(LOG_DEBUG, 'Got message forwarded from jid ' . $jid); - return $jid; - } - - function is_autoreply($txt) - { - if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) { - return true; - } else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) { - return true; - } else { - return false; - } - } - - function is_otr($txt) - { - if (preg_match('/^\?OTR/', $txt)) { - return true; - } else { - return false; - } - } - - function from_site($address, $msg) - { - $text = '['.common_config('site', 'name') . '] ' . $msg; - jabber_send_message($address, $text); - } - - function handle_command($user, $body) - { - $inter = new CommandInterpreter(); - $cmd = $inter->handle_command($user, $body); - if ($cmd) { - $chan = new XMPPChannel($this->conn); - $cmd->execute($chan); - return true; - } else { - return false; - } - } - - function add_notice(&$user, &$pl) - { - $body = trim($pl['body']); - $content_shortened = common_shorten_links($body); - if (Notice::contentTooLong($content_shortened)) { - $from = jabber_normalize_jid($pl['from']); - $this->from_site($from, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d.'), - Notice::maxContent(), - mb_strlen($content_shortened))); - return; - } - - try { - $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp'); - } catch (Exception $e) { - $this->log(LOG_ERR, $e->getMessage()); - $this->from_site($user->jabber, $e->getMessage()); - return; - } - - common_broadcast_notice($notice); - $this->log(LOG_INFO, - 'Added notice ' . $notice->id . ' from user ' . $user->nickname); - $notice->free(); - unset($notice); - } - - function handle_presence(&$pl) - { - $from = jabber_normalize_jid($pl['from']); - switch ($pl['type']) { - case 'subscribe': - # We let anyone subscribe - $this->subscribed($from); - $this->log(LOG_INFO, - 'Accepted subscription from ' . $from); - break; - case 'subscribed': - case 'unsubscribed': - case 'unsubscribe': - $this->log(LOG_INFO, - 'Ignoring "' . $pl['type'] . '" from ' . $from); - break; - default: - if (!$pl['type']) { - $user = User::staticGet('jabber', $from); - if (!$user) { - $this->log(LOG_WARNING, 'Presence from unknown user ' . $from); - return; - } - if ($user->updatefrompresence) { - $this->log(LOG_INFO, 'Updating ' . $user->nickname . - ' status from presence.'); - $this->add_notice($user, $pl); - } - $user->free(); - unset($user); - } - break; - } - unset($pl['xml']); - $pl['xml'] = null; - - $pl = null; - unset($pl); - } - - function log($level, $msg) - { - $text = 'XMPPDaemon('.$this->resource.'): '.$msg; - common_log($level, $text); - if (!$this->daemonize) - { - $line = common_log_line($level, $text); - echo $line; - echo "\n"; - } - } +} - function subscribed($to) - { - jabber_special_presence('subscribed', $to); +class XmppMaster extends IoMaster +{ + /** + * Initialize IoManagers for the currently configured site + * which are appropriate to this instance. + */ + function initManagers() + { + // @fixme right now there's a hack in QueueManager to determine + // which queues to subscribe to based on the master class. + $this->instantiate('QueueManager'); + $this->instantiate('XmppManager'); } } -- cgit v1.2.3-54-g00ecf From df9b780706eec9ff67e97803f5f70b475b010281 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 6 Oct 2009 15:29:22 -0400 Subject: action/doc.php is PHPCS clean --- actions/doc.php | 61 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/actions/doc.php b/actions/doc.php index 836f039d3..5df18a859 100644 --- a/actions/doc.php +++ b/actions/doc.php @@ -49,7 +49,7 @@ class DocAction extends Action var $title; /** - * Class handler. + * Handle a request * * @param array $args array of arguments * @@ -71,6 +71,7 @@ class DocAction extends Action } $c = file_get_contents($this->filename); + $this->output = common_markup_to_html($c); Event::handle('EndLoadDoc', array($this->title, &$this->output)); @@ -79,30 +80,48 @@ class DocAction extends Action $this->showPage(); } - // overrrided to add entry-title class - function showPageTitle() { + /** + * Page title + * + * Gives the page title of the document. Override default for hAtom entry. + * + * @return void + */ + + function showPageTitle() + { $this->element('h1', array('class' => 'entry-title'), $this->title()); } - // overrided to add hentry, and content-inner classes + /** + * Block for content. + * + * Overrides default from Action to wrap everything in an hAtom entry. + * + * @return void. + */ + function showContentBlock() - { - $this->elementStart('div', array('id' => 'content', 'class' => 'hentry')); - $this->showPageTitle(); - $this->showPageNoticeBlock(); - $this->elementStart('div', array('id' => 'content_inner', - 'class' => 'entry-content')); - // show the actual content (forms, lists, whatever) - $this->showContent(); - $this->elementEnd('div'); - $this->elementEnd('div'); - } + { + $this->elementStart('div', array('id' => 'content', 'class' => 'hentry')); + $this->showPageTitle(); + $this->showPageNoticeBlock(); + $this->elementStart('div', array('id' => 'content_inner', + 'class' => 'entry-content')); + // show the actual content (forms, lists, whatever) + $this->showContent(); + $this->elementEnd('div'); + $this->elementEnd('div'); + } /** * Display content. * - * @return nothing + * Shows the content of the document. + * + * @return void */ + function showContent() { $this->raw($this->output); @@ -111,6 +130,8 @@ class DocAction extends Action /** * Page title. * + * Uses the title of the document. + * * @return page title */ function title() @@ -118,6 +139,14 @@ class DocAction extends Action return ucfirst($this->title); } + /** + * These pages are read-only. + * + * @param array $args unused. + * + * @return boolean read-only flag (false) + */ + function isReadOnly($args) { return true; -- cgit v1.2.3-54-g00ecf From 9f815c968f855b75e82224d85314007ec0613aad Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 7 Oct 2009 05:14:25 -0400 Subject: restructure doc.php for new use --- actions/doc.php | 75 +++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/actions/doc.php b/actions/doc.php index 5df18a859..99c2c7966 100644 --- a/actions/doc.php +++ b/actions/doc.php @@ -45,8 +45,18 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { */ class DocAction extends Action { - var $filename; - var $title; + var $output = null; + var $filename = null; + var $title = null; + + function prepare($args) + { + $this->title = $this->trimmed('title'); + $this->output = null; + + $this->loadDoc(); + return true; + } /** * Handle a request @@ -58,25 +68,6 @@ class DocAction extends Action function handle($args) { parent::handle($args); - - $this->title = $this->trimmed('title'); - $this->output = null; - - if (Event::handle('StartLoadDoc', array(&$this->title, &$this->output))) { - - $this->filename = INSTALLDIR.'/doc-src/'.$this->title; - if (!file_exists($this->filename)) { - $this->clientError(_('No such document.')); - return; - } - - $c = file_get_contents($this->filename); - - $this->output = common_markup_to_html($c); - - Event::handle('EndLoadDoc', array($this->title, &$this->output)); - } - $this->showPage(); } @@ -151,4 +142,46 @@ class DocAction extends Action { return true; } + + function loadDoc() + { + if (Event::handle('StartLoadDoc', array(&$this->title, &$this->output))) { + + $this->filename = $this->getFilename(); + + if (empty($this->filename)) { + throw new ClientException(sprintf(_('No such document "%s"'), $this->title), 404); + } + + $c = file_get_contents($this->filename); + + $this->output = common_markup_to_html($c); + + Event::handle('EndLoadDoc', array($this->title, &$this->output)); + } + } + + function getFilename() + { + $local = array_merge(glob(INSTALLDIR.'/local/doc-src/'.$this->title), + glob(INSTALLDIR.'/local/doc-src/'.$this->title.'.*')); + + if (count($local)) { + return $this->negotiateLanguage($local); + } + + $dist = array_merge(glob(INSTALLDIR.'/doc-src/'.$this->title), + glob(INSTALLDIR.'/doc-src/'.$this->title.'.*')); + + if (count($dist)) { + return $this->negotiateLanguage($dist); + } + + return null; + } + + function negotiateLanguage($files) + { + // FIXME: write this + } } -- cgit v1.2.3-54-g00ecf From 104d300799c0817bdbe893f08fd9b46d37a75a80 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 22 Jan 2010 14:13:28 -0500 Subject: do actual language negotiation for help docs --- actions/doc.php | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/actions/doc.php b/actions/doc.php index 99c2c7966..25d363472 100644 --- a/actions/doc.php +++ b/actions/doc.php @@ -51,6 +51,8 @@ class DocAction extends Action function prepare($args) { + parent::prepare($args); + $this->title = $this->trimmed('title'); $this->output = null; @@ -163,25 +165,41 @@ class DocAction extends Action function getFilename() { - $local = array_merge(glob(INSTALLDIR.'/local/doc-src/'.$this->title), - glob(INSTALLDIR.'/local/doc-src/'.$this->title.'.*')); + if (file_exists(INSTALLDIR.'/local/doc-src/'.$this->title)) { + $localDef = INSTALLDIR.'/local/doc-src/'.$this->title; + } + + $local = glob(INSTALLDIR.'/local/doc-src/'.$this->title.'.*'); + + if (count($local) || isset($localDef)) { + return $this->negotiateLanguage($local, $localDef); + } - if (count($local)) { - return $this->negotiateLanguage($local); + if (file_exists(INSTALLDIR.'/doc-src/'.$this->title)) { + $distDef = INSTALLDIR.'/doc-src/'.$this->title; } - $dist = array_merge(glob(INSTALLDIR.'/doc-src/'.$this->title), - glob(INSTALLDIR.'/doc-src/'.$this->title.'.*')); + $dist = glob(INSTALLDIR.'/doc-src/'.$this->title.'.*'); - if (count($dist)) { - return $this->negotiateLanguage($dist); + if (count($dist) || isset($distDef)) { + return $this->negotiateLanguage($dist, $distDef); } return null; } - function negotiateLanguage($files) + function negotiateLanguage($filenames, $defaultFilename=null) { - // FIXME: write this + // XXX: do this better + + $langcode = common_language(); + + foreach ($filenames as $filename) { + if (preg_match('/\.'.$langcode.'$/', $filename)) { + return $filename; + } + } + + return $defaultFilename; } } -- cgit v1.2.3-54-g00ecf From 8bf2a9046bd364f684374b0388c8ea1a71b5ae0b Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 22 Jan 2010 19:18:14 +0100 Subject: Fixed innerHTML problem in IE7 and 8 for badge script --- js/identica-badge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/identica-badge.js b/js/identica-badge.js index 8276f22a1..e43d1c43c 100644 --- a/js/identica-badge.js +++ b/js/identica-badge.js @@ -223,7 +223,7 @@ function markupPost(raw, server) { }, changeUserTo : function(el) { $.a.user = el.rel; - $.s.h.a.innerHTML = el.rev + $.a.headerText; + $.s.h.a.appendChild(document.createTextNode(el.rev + $.a.headerText)); $.s.h.a.href = 'http://' + $.a.server + '/' + el.id; $.f.runSearch(); }, -- cgit v1.2.3-54-g00ecf From 99866a459bff927c4a72c17904d2ecbc1c421af3 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 22 Jan 2010 12:35:05 -0800 Subject: Fix for stuck queue messages: wrap processing in stomp transactions so our lack of an ACK if PHP dies actually triggers redelivery. Previously, messages once delivered would just get stuck in the queue seemingly forever if they never got ACKed. Note this could lead to partial duplication, for instance if the OMB or Twitter queue handlers die after 1/2 of the outgoing sends. Recommendations: * catch exceptions more aggressively within queue handlers (so only PHP fatal errors are likely to kill in the middle) * for processing that involves sending to multiple clients, consider a second queue similar to the XMPP output, eg for OMB: - first queue gets delivery list and builds message data, enqueueing it for each target address - second queue can handle each individual outgoing message (and attempt redelivery etc separately) This would also protect better against a recurring error preventing delivery in the second part, and could spread out any slow sends over multiple threads. --- lib/stompqueuemanager.php | 68 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index f057bd9e4..8f0091a13 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -41,6 +41,10 @@ class StompQueueManager extends QueueManager protected $sites = array(); + protected $useTransactions = true; + protected $transaction = null; + protected $transactionCount = 0; + function __construct() { parent::__construct(); @@ -201,6 +205,7 @@ class StompQueueManager extends QueueManager } else { $this->doSubscribe(); } + $this->begin(); return true; } @@ -213,6 +218,9 @@ class StompQueueManager extends QueueManager */ public function finish() { + // If there are any outstanding delivered messages we haven't processed, + // free them for another thread to take. + $this->rollback(); if ($this->sites) { foreach ($this->sites as $server) { StatusNet::init($server); @@ -293,7 +301,9 @@ class StompQueueManager extends QueueManager $notice = Notice::staticGet('id', $id); if (empty($notice)) { $this->_log(LOG_WARNING, "Skipping missing $info"); - $this->con->ack($frame); + $this->ack($frame); + $this->commit(); + $this->begin(); $this->stats('badnotice', $queue); return false; } @@ -308,7 +318,9 @@ class StompQueueManager extends QueueManager $handler = $this->getHandler($queue); if (!$handler) { $this->_log(LOG_ERROR, "Missing handler class; skipping $info"); - $this->con->ack($frame); + $this->ack($frame); + $this->commit(); + $this->begin(); $this->stats('badhandler', $queue); return false; } @@ -320,14 +332,18 @@ class StompQueueManager extends QueueManager // FIXME we probably shouldn't have to do // this kind of queue management ourselves; // if we don't ack, it should resend... - $this->con->ack($frame); + $this->ack($frame); $this->enqueue($item, $queue); + $this->commit(); + $this->begin(); $this->stats('requeued', $queue); return false; } $this->_log(LOG_INFO, "Successfully handled $info"); - $this->con->ack($frame); + $this->ack($frame); + $this->commit(); + $this->begin(); $this->stats('handled', $queue); return true; } @@ -369,5 +385,49 @@ class StompQueueManager extends QueueManager { common_log($level, 'StompQueueManager: '.$msg); } + + protected function begin() + { + if ($this->useTransactions) { + if ($this->transaction) { + throw new Exception("Tried to start transaction in the middle of a transaction"); + } + $this->transactionCount++; + $this->transaction = $this->master->id . '-' . $this->transactionCount . '-' . time(); + $this->con->begin($this->transaction); + } + } + + protected function ack($frame) + { + if ($this->useTransactions) { + if (!$this->transaction) { + throw new Exception("Tried to ack but not in a transaction"); + } + } + $this->con->ack($frame, $this->transaction); + } + + protected function commit() + { + if ($this->useTransactions) { + if (!$this->transaction) { + throw new Exception("Tried to commit but not in a transaction"); + } + $this->con->commit($this->transaction); + $this->transaction = null; + } + } + + protected function rollback() + { + if ($this->useTransactions) { + if (!$this->transaction) { + throw new Exception("Tried to rollback but not in a transaction"); + } + $this->con->commit($this->transaction); + $this->transaction = null; + } + } } -- cgit v1.2.3-54-g00ecf From 6d055ce09ea516f115a7c21a59d11f9257ce0d9e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 22 Jan 2010 13:49:05 -0800 Subject: Fix unqueuemanager for updated QueueHandler interface --- lib/unqueuemanager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/unqueuemanager.php b/lib/unqueuemanager.php index 5595eac05..785de7c8c 100644 --- a/lib/unqueuemanager.php +++ b/lib/unqueuemanager.php @@ -47,7 +47,7 @@ class UnQueueManager extends QueueManager $handler = $this->getHandler($queue); if ($handler) { - $handler->handle_notice($notice); + $handler->handle($notice); } else { if (Event::handle('UnqueueHandleNotice', array(&$notice, $queue))) { throw new ServerException("UnQueueManager: Unknown queue: $queue"); -- cgit v1.2.3-54-g00ecf From 71b3b9ee2be6973c3f55a59811ae103321c5abcc Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 22 Jan 2010 13:58:20 -0800 Subject: Consolidate PuSH publishing ping into a single POST for all feeds, and fix server response (if any on failure) to go to log instead of stdout. --- plugins/PubSubHubBub/PubSubHubBubPlugin.php | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/plugins/PubSubHubBub/PubSubHubBubPlugin.php b/plugins/PubSubHubBub/PubSubHubBubPlugin.php index 8286cd548..ce6086df9 100644 --- a/plugins/PubSubHubBub/PubSubHubBubPlugin.php +++ b/plugins/PubSubHubBub/PubSubHubBubPlugin.php @@ -211,13 +211,20 @@ class PubSubHubBubPlugin extends Plugin 'format' => 'atom')); } } - - foreach (array_unique($feeds) as $feed) { - if (!$publisher->publish_update($feed)) { - common_log_line(LOG_WARNING, - $feed.' was not published to hub at '. - $this->hub.':'.$publisher->last_response()); - } + $feeds = array_unique($feeds); + + ob_start(); + $ok = $publisher->publish_update($feeds); + $push_last_response = ob_get_clean(); + + if (!$ok) { + common_log(LOG_WARNING, + 'Failure publishing ' . count($feeds) . ' feeds to hub at '. + $this->hub.': '.$push_last_response); + } else { + common_log(LOG_INFO, + 'Published ' . count($feeds) . ' feeds to hub at '. + $this->hub.': '.$push_last_response); } return true; -- cgit v1.2.3-54-g00ecf From a4d733b68dad7f70857309b7da8e95cb164da746 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 22 Jan 2010 15:04:53 -0800 Subject: Fix for PoweredByStatusNetPlugin to be localizable (was broken for non-English word order) (Note the .po files will have to be added manually for now as we haven't set TranslateWiki up for plugins I think) --- .../PoweredByStatusNetPlugin.php | 5 ++-- .../locale/PoweredByStatusNet.po | 32 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po diff --git a/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php b/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php index c59fcca89..14d1608d3 100644 --- a/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php +++ b/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php @@ -46,8 +46,9 @@ class PoweredByStatusNetPlugin extends Plugin function onEndAddressData($action) { $action->elementStart('span', 'poweredby'); - $action->text(_('powered by')); - $action->element('a', array('href' => 'http://status.net/'), 'StatusNet'); + $action->raw(sprintf(_m('powered by %s'), + sprintf('%s', + _m('StatusNet')))); $action->elementEnd('span'); return true; diff --git a/plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po b/plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po new file mode 100644 index 000000000..bd39124ef --- /dev/null +++ b/plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po @@ -0,0 +1,32 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-01-22 15:03-0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: PoweredByStatusNetPlugin.php:49 +#, php-format +msgid "powered by %s" +msgstr "" + +#: PoweredByStatusNetPlugin.php:51 +msgid "StatusNet" +msgstr "" + +#: PoweredByStatusNetPlugin.php:64 +msgid "" +"Outputs powered by StatusNet after site " +"name." +msgstr "" -- cgit v1.2.3-54-g00ecf From 51fdba09d4bdc2b933e40c9ea5ea6de76c4aa799 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 21 Jan 2010 16:49:49 +0100 Subject: Event hooks for before and after site_notice --- EVENTS.txt | 6 ++++++ lib/action.php | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/EVENTS.txt b/EVENTS.txt index 6e6afa070..1ed670697 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -150,6 +150,12 @@ StartAddressData: Allows the site owner to provide additional information about EndAddressData: At the end of
- $action: the current action +StartShowSiteNotice: Before showing site notice +- $action: the current action + +EndShowSiteNotice: After showing site notice +- $action: the current action + StartLoginGroupNav: Before showing the login and register navigation menu - $action: the current action diff --git a/lib/action.php b/lib/action.php index 171bea17c..22425e682 100644 --- a/lib/action.php +++ b/lib/action.php @@ -373,7 +373,11 @@ class Action extends HTMLOutputter // lawsuit $this->elementStart('div', array('id' => 'header')); $this->showLogo(); $this->showPrimaryNav(); - $this->showSiteNotice(); + if (Event::handle('StartShowSiteNotice', array($this))) { + $this->showSiteNotice(); + + Event::handle('EndShowSiteNotice', array($this)); + } if (common_logged_in()) { $this->showNoticeForm(); } else { -- cgit v1.2.3-54-g00ecf From aaa2dd2712504dc9399d6cc5e1a002eb08ce6d4f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:02:55 +0000 Subject: Styles for application details page --- theme/base/css/display.css | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 2e4c88dfa..5e280bdfe 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -894,6 +894,39 @@ font-weight:normal; margin-right:11px; } +#showapplication .entity_profile { +width:68%; +} +#showapplication .entity_profile img { +max-width:96px; +max-height:96px; +} +#showapplication .entity_profile .entity_fn { +margin-left:0; +} +#showapplication .entity_profile .entity_fn .fn:before, +#showapplication .entity_profile .entity_fn .fn:after { +content:''; +} +#showapplication .entity_data { +clear:both; +margin-bottom:18px; +} +#showapplication .entity_data h2 { +display:none; +} +#showapplication .entity_data dl { +margin-bottom:18px; +} +#showapplication .entity_data dt { +font-weight:bold; +} +#showapplication .entity_data dd { +margin-left:1.795%; +font-family:monospace; +font-size:1.3em; +} + /* NOTICE */ .notice, .profile { -- cgit v1.2.3-54-g00ecf From f3c732587ca17d7fe693d2f3830bd65f3b1f678f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 02:04:20 +0000 Subject: Styles for application list --- theme/base/css/display.css | 16 +++++++++++++++- theme/default/css/display.css | 2 ++ theme/identica/css/display.css | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 5e280bdfe..c33f64aca 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -894,6 +894,19 @@ font-weight:normal; margin-right:11px; } +/*applications*/ +.applications { +margin-bottom:18px; +float:left; +width:100%; +} +.applications li { +list-style-type:none; +} +.application img { +max-width:96px; +max-height:96px; +} #showapplication .entity_profile { width:68%; } @@ -929,7 +942,8 @@ font-size:1.3em; /* NOTICE */ .notice, -.profile { +.profile, +.application { position:relative; padding-top:11px; padding-bottom:11px; diff --git a/theme/default/css/display.css b/theme/default/css/display.css index 8a2c01175..4a45303ca 100644 --- a/theme/default/css/display.css +++ b/theme/default/css/display.css @@ -129,6 +129,7 @@ color:#002FA7; .notice, .profile, +.application, #content tbody tr { border-top-color:#C8D1D5; } @@ -378,6 +379,7 @@ box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); -webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); } #content .notices li:hover, +#content .applications li:hover, #content tbody tr:hover { background-color:rgba(240, 240, 240, 0.2); } diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index 4ee48459d..96e3c61bc 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -129,6 +129,7 @@ color:#002FA7; .notice, .profile, +.application, #content tbody tr { border-top-color:#CEE1E9; } @@ -377,6 +378,7 @@ box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); -webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); } #content .notices li:hover, +#content .applications li:hover, #content tbody tr:hover { background-color:rgba(240, 240, 240, 0.2); } -- cgit v1.2.3-54-g00ecf From cae113941d9c2aadcbe4763a481b2bb25a205075 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 02:33:36 +0000 Subject: Added key icon for application key and secret rest action --- theme/base/images/icons/icons-01.gif | Bin 3607 -> 3650 bytes theme/base/images/icons/twotone/green/key.gif | Bin 0 -> 76 bytes theme/default/css/display.css | 6 +++++- theme/identica/css/display.css | 6 +++++- 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 theme/base/images/icons/twotone/green/key.gif diff --git a/theme/base/images/icons/icons-01.gif b/theme/base/images/icons/icons-01.gif index 06202a047..f93d33d79 100644 Binary files a/theme/base/images/icons/icons-01.gif and b/theme/base/images/icons/icons-01.gif differ diff --git a/theme/base/images/icons/twotone/green/key.gif b/theme/base/images/icons/twotone/green/key.gif new file mode 100644 index 000000000..ccf357ab2 Binary files /dev/null and b/theme/base/images/icons/twotone/green/key.gif differ diff --git a/theme/default/css/display.css b/theme/default/css/display.css index 4a45303ca..3aebb239d 100644 --- a/theme/default/css/display.css +++ b/theme/default/css/display.css @@ -188,7 +188,8 @@ button.close, .entity_delete input.submit, .notice-options .repeated, .form_notice label[for=notice_data-geo], -button.minimize { +button.minimize, +.form_reset_key input.submit { background-image:url(../../base/images/icons/icons-01.gif); background-repeat:no-repeat; background-color:transparent; @@ -333,6 +334,9 @@ background-position: 5px -1445px; .entity_delete input.submit { background-position: 5px -1511px; } +.form_reset_key input.submit { +background-position: 5px -1973px; +} /* NOTICES */ .notice .attachment { diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index 96e3c61bc..2818196c2 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -188,7 +188,8 @@ button.close, .entity_delete input.submit, .notice-options .repeated, .form_notice label[for=notice_data-geo], -button.minimize { +button.minimize, +.form_reset_key input.submit { background-image:url(../../base/images/icons/icons-01.gif); background-repeat:no-repeat; background-color:transparent; @@ -332,6 +333,9 @@ background-position: 5px -1445px; .entity_delete input.submit { background-position: 5px -1511px; } +.form_reset_key input.submit { +background-position: 5px -1973px; +} /* NOTICES */ .notice .attachment { -- cgit v1.2.3-54-g00ecf From e7c26eb443df709b9526d94d7103766baec114b2 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 02:51:33 +0000 Subject: Styles for image max width/height and radio form controls --- theme/base/css/display.css | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index c33f64aca..507733979 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -903,17 +903,15 @@ width:100%; .applications li { list-style-type:none; } -.application img { +.application img, +#showapplication .entity_profile img, +#editapplication .form_data #application_icon img { max-width:96px; max-height:96px; } #showapplication .entity_profile { width:68%; } -#showapplication .entity_profile img { -max-width:96px; -max-height:96px; -} #showapplication .entity_profile .entity_fn { margin-left:0; } @@ -939,6 +937,10 @@ margin-left:1.795%; font-family:monospace; font-size:1.3em; } +#editapplication .form_data #application_types label.radio, +#editapplication .form_data #default_access_types label.radio { +width:15%; +} /* NOTICE */ .notice, -- cgit v1.2.3-54-g00ecf From fa218ec65dde91f129eb17c7d443bec4017d170c Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Wed, 13 Jan 2010 22:05:22 -0500 Subject: Revert "Drop the Google Client API-based AJAX geolocation lookup shim -- it fails to ask for user permission, causing us quite a bit of difficulty." This reverts commit 749b8b5b8ca4d1c39d350879aadddbdb9d8b71d5. --- js/geometa.js | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 118 insertions(+), 5 deletions(-) diff --git a/js/geometa.js b/js/geometa.js index 87e3c99a1..21deb1885 100644 --- a/js/geometa.js +++ b/js/geometa.js @@ -1,4 +1,4 @@ -// A shim to implement the W3C Geolocation API Specification using Gears +// A shim to implement the W3C Geolocation API Specification using Gears or the Ajax API if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) (function(){ // -- BEGIN GEARS_INIT @@ -96,9 +96,122 @@ var GearsGeoLocation = (function() { }; }); -// If you have Gears installed use that -if (window.google && google.gears) { - navigator.geolocation = GearsGeoLocation(); -} +var AjaxGeoLocation = (function() { + // -- PRIVATE + var loading = false; + var loadGoogleLoader = function() { + if (!hasGoogleLoader() && !loading) { + loading = true; + var s = document.createElement('script'); + s.src = (document.location.protocol == "https:"?"https://":"http://") + 'www.google.com/jsapi?callback=_google_loader_apiLoaded'; + s.type = "text/javascript"; + document.getElementsByTagName('body')[0].appendChild(s); + } + }; + + var queue = []; + var addLocationQueue = function(callback) { + queue.push(callback); + } + + var runLocationQueue = function() { + if (hasGoogleLoader()) { + while (queue.length > 0) { + var call = queue.pop(); + call(); + } + } + } + + window['_google_loader_apiLoaded'] = function() { + runLocationQueue(); + } + + var hasGoogleLoader = function() { + return (window['google'] && google['loader']); + } + + var checkGoogleLoader = function(callback) { + if (hasGoogleLoader()) return true; + + addLocationQueue(callback); + + loadGoogleLoader(); + + return false; + }; + + loadGoogleLoader(); // start to load as soon as possible just in case + + // -- PUBLIC + return { + shim: true, + + type: "ClientLocation", + + lastPosition: null, + + getCurrentPosition: function(successCallback, errorCallback, options) { + var self = this; + if (!checkGoogleLoader(function() { + self.getCurrentPosition(successCallback, errorCallback, options); + })) return; + + if (google.loader.ClientLocation) { + var cl = google.loader.ClientLocation; + + var position = { + coords: { + latitude: cl.latitude, + longitude: cl.longitude, + altitude: null, + accuracy: 43000, // same as Gears accuracy over wifi? + altitudeAccuracy: null, + heading: null, + speed: null, + }, + // extra info that is outside of the bounds of the core API + address: { + city: cl.address.city, + country: cl.address.country, + country_code: cl.address.country_code, + region: cl.address.region + }, + timestamp: new Date() + }; + + successCallback(position); + + this.lastPosition = position; + } else if (errorCallback === "function") { + errorCallback({ code: 3, message: "Using the Google ClientLocation API and it is not able to calculate a location."}); + } + }, + + watchPosition: function(successCallback, errorCallback, options) { + this.getCurrentPosition(successCallback, errorCallback, options); + + var self = this; + var watchId = setInterval(function() { + self.getCurrentPosition(successCallback, errorCallback, options); + }, 10000); + + return watchId; + }, + + clearWatch: function(watchId) { + clearInterval(watchId); + }, + + getPermission: function(siteName, imageUrl, extraMessage) { + // for now just say yes :) + return true; + } + + }; +}); + +// If you have Gears installed use that, else use Ajax ClientLocation +navigator.geolocation = (window.google && google.gears) ? GearsGeoLocation() : AjaxGeoLocation(); })(); -- cgit v1.2.3-54-g00ecf From 414c80a18d6bbcb75e816a5f4ff10d6c54107d41 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 16 Jan 2010 19:16:44 +0000 Subject: Removed extra comma in object --- js/geometa.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/geometa.js b/js/geometa.js index 21deb1885..6bad095ec 100644 --- a/js/geometa.js +++ b/js/geometa.js @@ -168,7 +168,7 @@ var AjaxGeoLocation = (function() { accuracy: 43000, // same as Gears accuracy over wifi? altitudeAccuracy: null, heading: null, - speed: null, + speed: null }, // extra info that is outside of the bounds of the core API address: { -- cgit v1.2.3-54-g00ecf From ab53a8704b13a20f8ae885d2b92883e0c7101424 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 16 Jan 2010 19:42:32 +0000 Subject: Some JSlint-ing --- js/geometa.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/js/geometa.js b/js/geometa.js index 6bad095ec..bba59b448 100644 --- a/js/geometa.js +++ b/js/geometa.js @@ -1,5 +1,5 @@ // A shim to implement the W3C Geolocation API Specification using Gears or the Ajax API -if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) (function(){ +if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) { (function(){ // -- BEGIN GEARS_INIT (function() { @@ -23,8 +23,7 @@ if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) } } catch (e) { // Safari - if ((typeof navigator.mimeTypes != 'undefined') - && navigator.mimeTypes["application/x-googlegears"]) { + if ((typeof navigator.mimeTypes != 'undefined') && navigator.mimeTypes["application/x-googlegears"]) { factory = document.createElement("object"); factory.style.display = "none"; factory.width = 0; @@ -64,8 +63,8 @@ var GearsGeoLocation = (function() { return function(position) { callback(position); self.lastPosition = position; - } - } + }; + }; // -- PUBLIC return { @@ -112,7 +111,7 @@ var AjaxGeoLocation = (function() { var queue = []; var addLocationQueue = function(callback) { queue.push(callback); - } + }; var runLocationQueue = function() { if (hasGoogleLoader()) { @@ -121,18 +120,18 @@ var AjaxGeoLocation = (function() { call(); } } - } + }; window['_google_loader_apiLoaded'] = function() { runLocationQueue(); - } + }; var hasGoogleLoader = function() { return (window['google'] && google['loader']); - } + }; var checkGoogleLoader = function(callback) { - if (hasGoogleLoader()) return true; + if (hasGoogleLoader()) { return true; } addLocationQueue(callback); @@ -155,7 +154,7 @@ var AjaxGeoLocation = (function() { var self = this; if (!checkGoogleLoader(function() { self.getCurrentPosition(successCallback, errorCallback, options); - })) return; + })) { return; } if (google.loader.ClientLocation) { var cl = google.loader.ClientLocation; @@ -215,3 +214,4 @@ var AjaxGeoLocation = (function() { navigator.geolocation = (window.google && google.gears) ? GearsGeoLocation() : AjaxGeoLocation(); })(); +} -- cgit v1.2.3-54-g00ecf From 68c864c95a10b9018862c8ff3677c1185f1df496 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 16 Jan 2010 19:44:37 +0000 Subject: JSLinting on JSON --- js/util.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/js/util.js b/js/util.js index a749c5d9f..2d19b8df2 100644 --- a/js/util.js +++ b/js/util.js @@ -529,13 +529,13 @@ var SN = { // StatusNet $('#'+SN.C.S.NoticeDataGeo).attr('checked', true); var cookieValue = { - 'NLat': data.lat, - 'NLon': data.lon, - 'NLNS': lns, - 'NLID': lid, - 'NLN': NLN_text, - 'NLNU': location.url, - 'NDG': true + NLat: data.lat, + NLon: data.lon, + NLNS: lns, + NLID: lid, + NLN: NLN_text, + NLNU: location.url, + NDG: true }; $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue)); }); @@ -570,9 +570,9 @@ var SN = { // StatusNet $('#'+SN.C.S.NoticeLon).val(position.coords.longitude); var data = { - 'lat': position.coords.latitude, - 'lon': position.coords.longitude, - 'token': $('#token').val() + lat: position.coords.latitude, + lon: position.coords.longitude, + token: $('#token').val() }; getJSONgeocodeURL(geocodeURL, data); -- cgit v1.2.3-54-g00ecf From f043bc62a53250eaf383e9f36f95031fcd4e1160 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 16 Jan 2010 20:10:46 +0000 Subject: Added missing position paramater --- js/util.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/util.js b/js/util.js index 2d19b8df2..094731955 100644 --- a/js/util.js +++ b/js/util.js @@ -498,7 +498,7 @@ var SN = { // StatusNet $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled'); } - function getJSONgeocodeURL(geocodeURL, data) { + function getJSONgeocodeURL(geocodeURL, data, position) { $.getJSON(geocodeURL, data, function(location) { var lns, lid; @@ -575,7 +575,7 @@ var SN = { // StatusNet token: $('#token').val() }; - getJSONgeocodeURL(geocodeURL, data); + getJSONgeocodeURL(geocodeURL, data, position); }, function(error) { @@ -602,7 +602,7 @@ var SN = { // StatusNet 'token': $('#token').val() }; - getJSONgeocodeURL(geocodeURL, data); + getJSONgeocodeURL(geocodeURL, data, position); } else { removeNoticeDataGeo(); -- cgit v1.2.3-54-g00ecf From 2a1ab137cc233314b4c115d0687c27c7dbaec96f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 16 Jan 2010 20:57:18 +0000 Subject: Using visibility:hidden instead of display:none for checkbox --- theme/base/css/display.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 507733979..fc11a9234 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -73,7 +73,7 @@ input.checkbox, input.radio { position:relative; top:2px; -left:0; +left:auto; border:0; } @@ -567,7 +567,8 @@ float:right; font-size:0.8em; } -.form_notice #notice_data-geo_wrap label { +.form_notice #notice_data-geo_wrap label, +.form_notice #notice_data-geo_wrap input { position:absolute; top:25px; right:4px; @@ -578,7 +579,7 @@ height:16px; display:block; } .form_notice #notice_data-geo_wrap input { -display:none; +visibility:hidden; } .form_notice #notice_data-geo_wrap label { font-weight:normal; -- cgit v1.2.3-54-g00ecf From 37e642bece04a208a7c7b0bffe8bb1dc3f976d1e Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 17 Jan 2010 14:04:47 +0000 Subject: Inline script for maxlength is deprecated --- plugins/MobileProfile/MobileProfilePlugin.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 14d2500e8..d426fc282 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -356,8 +356,6 @@ class MobileProfilePlugin extends WAP20Plugin $contentLimit = Notice::maxContent(); - $form->out->inlineScript('maxLength = ' . $contentLimit . ';'); - if ($contentLimit > 0) { $form->out->element('div', array('id' => 'notice_text-count'), $contentLimit); -- cgit v1.2.3-54-g00ecf From 2742494fe82d5c12016d46e2de5a849efe2ebee6 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 17 Jan 2010 19:45:35 +0000 Subject: Updated UI for notice aside content and notice options in MobileProfile --- plugins/MobileProfile/mp-screen.css | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 3eefc0c8e..472fbb001 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -179,10 +179,12 @@ padding-bottom:4px; } .notice div.entry-content { margin-left:0; -width:62.5%; +width:75%; +max-width:100%; +min-width:0; } .notice-options { -width:34%; +width:50px; margin-right:1%; } @@ -190,12 +192,29 @@ margin-right:1%; width:16px; height:16px; } -.notice-options a, -.notice-options input { +.notice .notice-options a, +.notice .notice-options input { box-shadow:none; -moz-box-shadow:none; -webkit-box-shadow:none; } +.notice .notice-options a, +.notice .notice-options form { +margin:-4px 0 0 0; +} +.notice .notice-options .notice_repeat, +.notice .notice-options .notice_delete { +margin-top:18px; +} +.notice .notice-options .notice_reply, +.notice .notice-options .notice_repeat { +margin-left:18px; +} + + +.notice .notice-options .notice_delete { +float:left; +} .entity_profile { width:auto; -- cgit v1.2.3-54-g00ecf From 940fc463c814ac26cdec9e3afdf67fd9187519a3 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 17 Jan 2010 22:31:47 +0000 Subject: Took out focus out of textare when location share is enabled/disabled. Also avoids the conflict with the URL fragment on the conversation page. --- js/util.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/js/util.js b/js/util.js index 094731955..d93467d4a 100644 --- a/js/util.js +++ b/js/util.js @@ -628,8 +628,6 @@ var SN = { // StatusNet else { removeNoticeDataGeo(); } - - $('#'+SN.C.S.NoticeDataText).focus(); }).change(); } }, -- cgit v1.2.3-54-g00ecf From f92a9dad8c68c8a1355a2f00b29d21209b62dd34 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 18 Jan 2010 11:12:05 +0000 Subject: Moved farbtastic's stylesheet to use relative paths for its own images --- js/farbtastic/farbtastic.css | 32 ++++++++++++++++++++++++++++++++ theme/base/css/farbtastic.css | 32 -------------------------------- 2 files changed, 32 insertions(+), 32 deletions(-) create mode 100644 js/farbtastic/farbtastic.css delete mode 100644 theme/base/css/farbtastic.css diff --git a/js/farbtastic/farbtastic.css b/js/farbtastic/farbtastic.css new file mode 100644 index 000000000..a88e7b868 --- /dev/null +++ b/js/farbtastic/farbtastic.css @@ -0,0 +1,32 @@ +.farbtastic { + position: relative; +} +.farbtastic * { + position: absolute; + cursor: crosshair; +} +.farbtastic, .farbtastic .wheel { + width: 195px; + height: 195px; +} +.farbtastic .color, .farbtastic .overlay { + top: 47px; + left: 47px; + width: 101px; + height: 101px; +} +.farbtastic .wheel { + background: url(wheel.png) no-repeat; + width: 195px; + height: 195px; +} +.farbtastic .overlay { + background: url(mask.png) no-repeat; +} +.farbtastic .marker { + width: 17px; + height: 17px; + margin: -8px 0 0 -8px; + overflow: hidden; + background: url(marker.png) no-repeat; +} diff --git a/theme/base/css/farbtastic.css b/theme/base/css/farbtastic.css deleted file mode 100644 index 7efcc73c3..000000000 --- a/theme/base/css/farbtastic.css +++ /dev/null @@ -1,32 +0,0 @@ -.farbtastic { - position: relative; -} -.farbtastic * { - position: absolute; - cursor: crosshair; -} -.farbtastic, .farbtastic .wheel { - width: 195px; - height: 195px; -} -.farbtastic .color, .farbtastic .overlay { - top: 47px; - left: 47px; - width: 101px; - height: 101px; -} -.farbtastic .wheel { - background: url(../../../js/farbtastic/wheel.png) no-repeat; - width: 195px; - height: 195px; -} -.farbtastic .overlay { - background: url(../../../js/farbtastic/mask.png) no-repeat; -} -.farbtastic .marker { - width: 17px; - height: 17px; - margin: -8px 0 0 -8px; - overflow: hidden; - background: url(../../../js/farbtastic/marker.png) no-repeat; -} -- cgit v1.2.3-54-g00ecf From 0f3658d3da788b071da408839195526a10da5e2e Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 18 Jan 2010 11:29:05 +0000 Subject: Updated path to farbtastic stylesheet --- actions/designadminpanel.php | 2 +- lib/designsettings.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/designadminpanel.php b/actions/designadminpanel.php index f862aff0e..72ad6ade2 100644 --- a/actions/designadminpanel.php +++ b/actions/designadminpanel.php @@ -289,7 +289,7 @@ class DesignadminpanelAction extends AdminPanelAction function showStylesheets() { parent::showStylesheets(); - $this->cssLink('css/farbtastic.css','base','screen, projection, tv'); + $this->cssLink('js/farbtastic/farbtastic.css',null,'screen, projection, tv'); } /** diff --git a/lib/designsettings.php b/lib/designsettings.php index b70ba0dfc..8e44c03a9 100644 --- a/lib/designsettings.php +++ b/lib/designsettings.php @@ -314,7 +314,7 @@ class DesignSettingsAction extends AccountSettingsAction function showStylesheets() { parent::showStylesheets(); - $this->cssLink('css/farbtastic.css','base','screen, projection, tv'); + $this->cssLink('js/farbtastic/farbtastic.css',null,'screen, projection, tv'); } /** -- cgit v1.2.3-54-g00ecf From c367e09d1130d4d4f01e029c4659b18f55ae6d8f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 18 Jan 2010 12:55:14 +0000 Subject: Some JS cleaning up for NoticeLocationAttach (which fixes also fixes a few bugs in WebKit) --- js/util.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/js/util.js b/js/util.js index d93467d4a..a7339010a 100644 --- a/js/util.js +++ b/js/util.js @@ -498,7 +498,7 @@ var SN = { // StatusNet $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled'); } - function getJSONgeocodeURL(geocodeURL, data, position) { + function getJSONgeocodeURL(geocodeURL, data) { $.getJSON(geocodeURL, data, function(location) { var lns, lid; @@ -513,7 +513,7 @@ var SN = { // StatusNet } if (typeof(location.name) == 'undefined') { - NLN_text = position.coords.latitude + ';' + position.coords.longitude; + NLN_text = data.lat + ';' + data.lon; } else { NLN_text = location.name; @@ -575,7 +575,7 @@ var SN = { // StatusNet token: $('#token').val() }; - getJSONgeocodeURL(geocodeURL, data, position); + getJSONgeocodeURL(geocodeURL, data); }, function(error) { @@ -597,12 +597,12 @@ var SN = { // StatusNet else { if (NLat.length > 0 && NLon.length > 0) { var data = { - 'lat': NLat, - 'lon': NLon, - 'token': $('#token').val() + lat: NLat, + lon: NLon, + token: $('#token').val() }; - getJSONgeocodeURL(geocodeURL, data, position); + getJSONgeocodeURL(geocodeURL, data); } else { removeNoticeDataGeo(); -- cgit v1.2.3-54-g00ecf From c3ee1af7bee3b7268fc4cfd40ef45d9ca512fe97 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 18 Jan 2010 17:17:02 +0000 Subject: Missing null className for incoming email form legend --- actions/emailsettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/emailsettings.php b/actions/emailsettings.php index bfef2970d..08608348c 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -130,7 +130,7 @@ class EmailsettingsAction extends AccountSettingsAction if (common_config('emailpost', 'enabled') && $user->email) { $this->elementStart('fieldset', array('id' => 'settings_email_incoming')); - $this->element('legend',_('Incoming email')); + $this->element('legend', null, _('Incoming email')); if ($user->incomingemail) { $this->elementStart('p'); $this->element('span', 'address', $user->incomingemail); -- cgit v1.2.3-54-g00ecf From e3ee5663abfc5fa873f4b2c76f6f89971c3c9aae Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 20 Jan 2010 18:32:24 +0100 Subject: Updated notice item view where a) notice text no longer wraps around (under author's photo) b) supplemental notice content and options will start right under notice text. --- plugins/MobileProfile/mp-screen.css | 12 +++++++++++- theme/base/css/display.css | 22 +++++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 472fbb001..76071352d 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -176,8 +176,18 @@ margin-bottom:0; .profile { padding-top:4px; padding-bottom:4px; +min-height:65px; } -.notice div.entry-content { +#content .notice .entry-title { +float:left; +width:100%; +margin-left:0; +} +#content .notice .author .photo { +position:static; +float:left; +} +#content .notice div.entry-content { margin-left:0; width:75%; max-width:100%; diff --git a/theme/base/css/display.css b/theme/base/css/display.css index fc11a9234..82670c964 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1006,6 +1006,16 @@ float:left; #shownotice .vcard .photo { margin-bottom:4px; } +#content .notice .author .photo { +position:absolute; +top:11px; +left:0; +float:none; +} +#content .notice .entry-title { +margin-left:59px; +} + .vcard .url { text-decoration:none; } @@ -1014,13 +1024,19 @@ text-decoration:underline; } .notice .entry-title { -float:left; -width:100%; overflow:hidden; } .notice .entry-title.ov { overflow:visible; } +#showstream .notice .entry-title, +#showstream .notice div.entry-content { +margin-left:0; +} +#shownotice .notice .entry-title, +#shownotice .notice div.entry-content { +margin-left:110px; +} #shownotice .notice .entry-title { font-size:2.2em; } @@ -1050,7 +1066,6 @@ max-width:70%; } #showstream .notice div.entry-content, #shownotice .notice div.entry-content { -margin-left:0; max-width:79%; } @@ -1114,6 +1129,7 @@ position:relative; font-size:0.95em; width:113px; float:right; +margin-top:3px; margin-right:4px; } -- cgit v1.2.3-54-g00ecf From 51775e38bab8f3c41aa19bed84a38d1a7f534e37 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 20 Jan 2010 18:50:48 +0100 Subject: Better alignment for notice options in MobileProfile --- plugins/MobileProfile/mp-screen.css | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 76071352d..04fa5fb00 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -194,7 +194,7 @@ max-width:100%; min-width:0; } .notice-options { -width:50px; +width:43px; margin-right:1%; } @@ -202,6 +202,13 @@ margin-right:1%; width:16px; height:16px; } +.notice-options form.processing { +background-image:none; +} +#wrap .notice-options form.processing input.submit { +background-position:0 47%; +} + .notice .notice-options a, .notice .notice-options input { box-shadow:none; @@ -212,16 +219,16 @@ box-shadow:none; .notice .notice-options form { margin:-4px 0 0 0; } -.notice .notice-options .notice_repeat, +.notice .notice-options .form_repeat, .notice .notice-options .notice_delete { -margin-top:18px; +margin-top:11px; } -.notice .notice-options .notice_reply, -.notice .notice-options .notice_repeat { -margin-left:18px; +.notice .notice-options .form_favor, +.notice .notice-options .form_disfavor, +.notice .notice-options .form_repeat { +margin-right:11px; } - .notice .notice-options .notice_delete { float:left; } -- cgit v1.2.3-54-g00ecf From fed111f08aeba6be5bfc5e924975048e6a8f6521 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 21 Jan 2010 13:23:04 +0100 Subject: Removed mobile stylesheet from core output. If Mobile support is seeked, MobileProfile plugin should be used. --- lib/action.php | 4 -- theme/base/css/mobile.css | 150 ---------------------------------------------- 2 files changed, 154 deletions(-) delete mode 100644 theme/base/css/mobile.css diff --git a/lib/action.php b/lib/action.php index 22425e682..e24277558 100644 --- a/lib/action.php +++ b/lib/action.php @@ -199,10 +199,6 @@ class Action extends HTMLOutputter // lawsuit if (Event::handle('StartShowStatusNetStyles', array($this)) && Event::handle('StartShowLaconicaStyles', array($this))) { $this->cssLink('css/display.css',null,'screen, projection, tv'); - if (common_config('site', 'mobile')) { - // TODO: "handheld" CSS for other mobile devices - $this->cssLink('css/mobile.css','base','only screen and (max-device-width: 480px)'); // Mobile WebKit - } $this->cssLink('css/print.css','base','print'); Event::handle('EndShowStatusNetStyles', array($this)); Event::handle('EndShowLaconicaStyles', array($this)); diff --git a/theme/base/css/mobile.css b/theme/base/css/mobile.css deleted file mode 100644 index f6c53ea8d..000000000 --- a/theme/base/css/mobile.css +++ /dev/null @@ -1,150 +0,0 @@ -/** theme: base - * - * @package StatusNet - * @author Meitar Moscovitz - * @author Sarven Capadisli - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -body { -font-size:2.5em; -} - -#wrap { -width:95%; -} - -#header, -#header address, -#anon_notice, -#site_nav_local_views .nav, -#form_notice, -#form_notice .form_data li, -#core, -#content_inner, -#notices_primary, -.notice, -.notice .entry-title, -.notice div.entry-content, -.notice-options, -.notice .notice-options a, -.pagination, -.pagination .nav, -.aside .section { -float:none; -} - -.notice-options .notice_reply, -.notice-options .notice_delete, -.notice-options .form_favor, -.notice-options .form_disfavor { -position:static; -} - -#form_notice, -#anon_notice, -#footer, -#form_notice .form_actions input.submit { -width:auto; -} - -.form_settings label { -width:25%; -} -.form_settings .form_data p.form_guide { -margin-left:26%; -} - -#site_nav_global_primary { -width:75%; -} - -.entity_profile { -width:65%; -} -.entity_actions { -margin-left:0; -} - -#form_notice, -#anon_notice { -clear:both; -} - -#content, -#aside_primary { -width:96%; -padding-left:2%; -padding-right:2%; -} - -#site_notice { -position:static; -float:right; -clear:right; -width:75%; -margin-right:0; -margin-bottom:11px; -} - -.notices { -font-size:1.5em; -} - -#form_notice textarea { -width:80%; -height:5em; -} -#form_notice .form_note { -right:20%; -top:6em; -} - - -.vcard .photo, -.section .vcard .photo { -margin-right:18px; -} -.notice, -.profile { -margin-bottom:18px; -} - -.notices .entry-title, -.notices div.entry-content { -width:90%; -} -.notice div.entry-content { -margin-left:0; -} - -.notice .author .photo { -height:4.5em; -width:4.5em; -} -.notice-options { -position:absolute; -top:0; -right:0; -padding-left:7%; -width:3%; -} - -.notice-options .notice_delete a { -float:left; -} -.pagination .nav { -overflow:auto; -} - -#export_data { -display:none; -} - -#site_nav_local_views li { -margin-right:4px; -} -#site_nav_local_views a { -padding:18px 11px; -} -- cgit v1.2.3-54-g00ecf From 38fe4ad9586706c69b46d68612d5a20415433ae7 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 24 Jan 2010 15:34:40 +0100 Subject: Added version info for MobileProfile plugin --- plugins/MobileProfile/MobileProfilePlugin.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index d426fc282..5c913836d 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -414,7 +414,15 @@ class MobileProfilePlugin extends WAP20Plugin return $proto.'://'.$serverpart.'/'.$pathpart.$relative; } -} - -?> + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'MobileProfile', + 'version' => STATUSNET_VERSION, + 'author' => 'Sarven Capadisli', + 'homepage' => 'http://status.net/wiki/Plugin:MobileProfile', + 'rawdescription' => + _m('XHTML MobileProfile output for supporting user agents.')); + return true; + } +} -- cgit v1.2.3-54-g00ecf From 238998eca017a3ddd6cc0c01a75f8ace37661b5d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 18:18:24 -0500 Subject: save nickname and wildcard when setting up status network --- classes/Status_network.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/classes/Status_network.php b/classes/Status_network.php index ef8e1ed43..445f8a5a3 100644 --- a/classes/Status_network.php +++ b/classes/Status_network.php @@ -48,6 +48,7 @@ class Status_network extends DB_DataObject static $cache = null; static $base = null; + static $wildcard = null; /** * @param string $dbhost @@ -187,7 +188,12 @@ class Status_network extends DB_DataObject $config['db']['database'] = "mysqli://$dbuser:$dbpass@$dbhost/$dbname"; - $config['site']['name'] = $sn->sitename; + $config['site']['name'] = $sn->sitename; + $config['site']['nickname'] = $sn->nickname; + + self::$wildcard = $wildcard; + + $config['site']['wildcard'] =& self::$wildcard; if (!empty($sn->hostname)) { $config['site']['server'] = $sn->hostname; @@ -230,4 +236,13 @@ class Status_network extends DB_DataObject exit; } + + function getServerName() + { + if (!empty($this->hostname)) { + return $this->hostname; + } else { + return $this->nickname . '.' . self::$wildcard; + } + } } -- cgit v1.2.3-54-g00ecf From 7cd666ae928b003a8096289009bfd7290f8f6c73 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 18:19:13 -0500 Subject: defaults for nickname and wildcard --- lib/default.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/default.php b/lib/default.php index 0cb70de2d..e3a043de1 100644 --- a/lib/default.php +++ b/lib/default.php @@ -30,6 +30,8 @@ $default = array('site' => array('name' => 'Just another StatusNet microblog', + 'nickname' => 'statusnet', + 'wildcard' => null, 'server' => $_server, 'theme' => 'default', 'path' => $_path, -- cgit v1.2.3-54-g00ecf From 60ce2ff3d01f22900d62b3d30ed89bc5e9d55167 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Sun, 24 Jan 2010 15:44:09 -0800 Subject: Use new StatusNetwork->serverName() to get full domain for wildcard config until we rebuild queues to be based on nicknames. Fixes live bug with new *.status.net sites breaking queuedaemon.php --- lib/iomaster.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iomaster.php b/lib/iomaster.php index 004e92b3e..3bf82bc6b 100644 --- a/lib/iomaster.php +++ b/lib/iomaster.php @@ -88,7 +88,7 @@ abstract class IoMaster $sn = new Status_network(); $sn->find(); while ($sn->fetch()) { - $hosts[] = $sn->hostname; + $hosts[] = $sn->getServerName(); } return $hosts; } -- cgit v1.2.3-54-g00ecf From c0b832d19fc478d67752a6692bf71f178346e14a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 10 Nov 2009 17:10:56 -0800 Subject: Add new OAuth application tables and DataObjects. Also add a new column for consumer secret to consumer table. --- classes/Consumer.php | 5 +++-- classes/Oauth_application.php | 33 +++++++++++++++++++++++++++++++++ classes/Oauth_application_user.php | 24 ++++++++++++++++++++++++ db/statusnet.sql | 26 ++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100755 classes/Oauth_application.php create mode 100755 classes/Oauth_application_user.php diff --git a/classes/Consumer.php b/classes/Consumer.php index d5b7b7e33..d17f183a8 100644 --- a/classes/Consumer.php +++ b/classes/Consumer.php @@ -11,9 +11,10 @@ class Consumer extends Memcached_DataObject public $__table = 'consumer'; // table name public $consumer_key; // varchar(255) primary_key not_null + public $consumer_secret; // varchar(255) not_null public $seed; // char(32) not_null - public $created; // datetime() not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + public $created; // datetime not_null + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP /* Static get */ function staticGet($k,$v=null) diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php new file mode 100755 index 000000000..6ad2db6dd --- /dev/null +++ b/classes/Oauth_application.php @@ -0,0 +1,33 @@ + Date: Thu, 12 Nov 2009 19:34:13 -0800 Subject: Changed the OAuth app tables to refer to profiles instead of users. Added an owner column to oauth_application. --- classes/Oauth_application.php | 23 ++++++++++++----------- classes/Oauth_application_user.php | 14 +++++++------- db/statusnet.sql | 9 +++++---- 3 files changed, 24 insertions(+), 22 deletions(-) mode change 100755 => 100644 classes/Oauth_application.php mode change 100755 => 100644 classes/Oauth_application_user.php diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php old mode 100755 new mode 100644 index 6ad2db6dd..e2862bf97 --- a/classes/Oauth_application.php +++ b/classes/Oauth_application.php @@ -2,32 +2,33 @@ /** * Table Definition for oauth_application */ -require_once 'classes/Memcached_DataObject'; +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Oauth_application extends Memcached_DataObject +class Oauth_application extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ public $__table = 'oauth_application'; // table name public $id; // int(4) primary_key not_null + public $owner; // int(4) not_null public $consumer_key; // varchar(255) not_null public $name; // varchar(255) not_null - public $description; // varchar(255) + public $description; // varchar(255) public $icon; // varchar(255) not_null - public $source_url; // varchar(255) - public $organization; // varchar(255) - public $homepage; // varchar(255) + public $source_url; // varchar(255) + public $organization; // varchar(255) + public $homepage; // varchar(255) public $callback_url; // varchar(255) not_null - public $type; // tinyint(1) - public $access_type; // tinyint(1) + public $type; // tinyint(1) + public $access_type; // tinyint(1) public $created; // datetime not_null public $modified; // timestamp not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=null) - { return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v); } - + function staticGet($k,$v=NULL) { + return Memcached_DataObject::staticGet('Oauth_application',$k,$v); + } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE } diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php old mode 100755 new mode 100644 index a8922f5e7..9e45ece25 --- a/classes/Oauth_application_user.php +++ b/classes/Oauth_application_user.php @@ -2,23 +2,23 @@ /** * Table Definition for oauth_application_user */ -require_once 'classes/Memcached_DataObject'; +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Oauth_application_user extends Memcached_DataObject +class Oauth_application_user extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ public $__table = 'oauth_application_user'; // table name - public $user_id; // int(4) primary_key not_null + public $profile_id; // int(4) primary_key not_null public $application_id; // int(4) primary_key not_null - public $access_type; // tinyint(1) + public $access_type; // tinyint(1) public $created; // datetime not_null /* Static get */ - function staticGet($k,$v=null) - { return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v); } - + function staticGet($k,$v=NULL) { + return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v); + } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE } diff --git a/db/statusnet.sql b/db/statusnet.sql index be0d14092..03e6115e5 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -210,6 +210,7 @@ create table nonce ( create table oauth_application ( id integer auto_increment primary key comment 'unique identifier', + owner integer not null comment 'owner of the application' references profile (id), consumer_key varchar(255) not null comment 'application consumer key' references consumer (consumer_key), name varchar(255) not null comment 'name of the application', description varchar(255) comment 'description of the application', @@ -219,18 +220,18 @@ create table oauth_application ( homepage varchar(255) comment 'homepage for the organization', callback_url varchar(255) not null comment 'url to redirect to after authentication', type tinyint default 0 comment 'type of app, 0 = browser, 1 = desktop', - access_type tinyint default 0 comment 'default access type, 0 = read-write, 1 = read-only', + access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified' ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; create table oauth_application_user ( - user_id integer not null comment 'id of the application user' references user (id), + profile_id integer not null comment 'user of the application' references profile (id), application_id integer not null comment 'id of the application' references oauth_application (id), - access_type tinyint default 0 comment 'access type, 0 = read-write, 1 = read-only', + access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked', created datetime not null comment 'date this record was created', - constraint primary key (user_id, application_id) + constraint primary key (profile_id, application_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; /* These are used by JanRain OpenID library */ -- cgit v1.2.3-54-g00ecf From ae46bc5fff21120ef867fb8ab59833a8f26adf47 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 12 Nov 2009 19:42:18 -0800 Subject: Started work on interface for displaying connected OAuth apps --- actions/applicationsettings.php | 135 ++++++++++++++++++++++++++++++++++++++++ actions/oauthclients.php | 108 ++++++++++++++++++++++++++++++++ classes/Profile.php | 23 +++++++ lib/applicationlist.php | 111 +++++++++++++++++++++++++++++++++ lib/connectsettingsaction.php | 4 +- lib/router.php | 4 +- 6 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 actions/applicationsettings.php create mode 100644 actions/oauthclients.php create mode 100644 lib/applicationlist.php diff --git a/actions/applicationsettings.php b/actions/applicationsettings.php new file mode 100644 index 000000000..16c571fee --- /dev/null +++ b/actions/applicationsettings.php @@ -0,0 +1,135 @@ +. + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/connectsettingsaction.php'; +require_once INSTALLDIR . '/lib/applicationlist.php'; + +/** + * Show connected OAuth applications + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see SettingsAction + */ + +class ApplicationSettingsAction extends ConnectSettingsAction +{ + /** + * Title of the page + * + * @return string Title of the page + */ + + function title() + { + return _('Connected Applications'); + } + + /** + * Instructions for use + * + * @return instructions for use + */ + + function getInstructions() + { + return _('You have allowed the following applications to access you account.'); + } + + /** + * Content area of the page + * + * @return void + */ + + function showContent() + { + $user = common_current_user(); + $profile = $user->getProfile(); + + $offset = ($this->page - 1) * APPS_PER_PAGE; + $limit = APPS_PER_PAGE + 1; + + $application = $profile->getApplications($offset, $limit); + + if ($application) { + $al = new ApplicationList($application, $this->user, $this); + $cnt = $al->show(); + if (0 == $cnt) { + $this->showEmptyListMessage(); + } + } + + $this->pagination($this->page > 1, $cnt > APPS_PER_PAGE, + $this->page, 'applicationsettings', + array('nickname' => $this->user->nickname)); + } + + /** + * Handle posts to this form + * + * Based on the button that was pressed, muxes out to other functions + * to do the actual task requested. + * + * All sub-functions reload the form with a message -- success or failure. + * + * @return void + */ + + function handlePost() + { + // CSRF protection + + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + } + + function showEmptyListMessage() + { + $message = sprintf(_('You have not authorized any applications to use your account.')); + + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); + } + +} diff --git a/actions/oauthclients.php b/actions/oauthclients.php new file mode 100644 index 000000000..9a29e158e --- /dev/null +++ b/actions/oauthclients.php @@ -0,0 +1,108 @@ +. + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/connectsettingsaction.php'; + +/** + * Show a user's registered OAuth applications + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see SettingsAction + */ + +class OauthClientsAction extends ConnectSettingsAction +{ + /** + * Title of the page + * + * @return string Title of the page + */ + + function title() + { + return _('Applications using %%site_name%%'); + } + + /** + * Instructions for use + * + * @return instructions for use + */ + + function getInstructions() + { + return _('Applications you have registered'); + } + + /** + * Content area of the page + * + * @return void + */ + + function showContent() + { + $user = common_current_user(); + + } + + /** + * Handle posts to this form + * + * Based on the button that was pressed, muxes out to other functions + * to do the actual task requested. + * + * All sub-functions reload the form with a message -- success or failure. + * + * @return void + */ + + function handlePost() + { + // CSRF protection + + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + } + +} diff --git a/classes/Profile.php b/classes/Profile.php index 25d908dbf..687215b11 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -352,6 +352,29 @@ class Profile extends Memcached_DataObject return $profile; } + function getApplications($offset = 0, $limit = null) + { + $qry = + 'SELECT oauth_application_user.* ' . + 'FROM oauth_application_user ' . + 'WHERE profile_id = %d ' . + 'ORDER BY created DESC '; + + if ($offset > 0) { + if (common_config('db','type') == 'pgsql') { + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } else { + $qry .= ' LIMIT ' . $offset . ', ' . $limit; + } + } + + $application = new Oauth_application(); + + $cnt = $application->query(sprintf($qry, $this->id)); + + return $application; + } + function subscriptionCount() { $c = common_memcache(); diff --git a/lib/applicationlist.php b/lib/applicationlist.php new file mode 100644 index 000000000..fed784bb6 --- /dev/null +++ b/lib/applicationlist.php @@ -0,0 +1,111 @@ +. + * + * @category Public + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/widget.php'; + +define('APPS_PER_PAGE', 20); + +/** + * Widget to show a list of OAuth applications + * + * @category Public + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApplicationList extends Widget +{ + /** Current application, application query */ + var $application = null; + + /** Owner of this list */ + var $owner = null; + + /** Action object using us. */ + var $action = null; + + function __construct($application, $owner=null, $action=null) + { + parent::__construct($action); + + $this->application = $application; + $this->owner = $owner; + $this->action = $action; + } + + function show() + { + $this->out->elementStart('ul', 'applications xoxo'); + + $cnt = 0; + + while ($this->application->fetch()) { + $cnt++; + if($cnt > APPS_PER_PAGE) { + break; + } + $this->showapplication(); + } + + $this->out->elementEnd('ul'); + + return $cnt; + } + + function showApplication() + { + $this->out->elementStart('li', array('class' => 'application', + 'id' => 'oauthclient-' . $this->application->id)); + + $user = common_current_user(); + + $this->out->raw($this->application->name); + + $this->out->elementEnd('li'); + } + + /* Override this in subclasses. */ + + function showOwnerControls() + { + return; + } + + function highlight($text) + { + return htmlspecialchars($text); + } +} diff --git a/lib/connectsettingsaction.php b/lib/connectsettingsaction.php index e5fb8727b..4b5059540 100644 --- a/lib/connectsettingsaction.php +++ b/lib/connectsettingsaction.php @@ -116,6 +116,9 @@ class ConnectSettingsNav extends Widget _('Updates by SMS')); } + $menu['applicationsettings'] = array(_('Applications'), + _('OAuth connected applications')); + foreach ($menu as $menuaction => $menudesc) { $this->action->menuItem(common_local_url($menuaction), $menudesc[0], @@ -131,4 +134,3 @@ class ConnectSettingsNav extends Widget } - diff --git a/lib/router.php b/lib/router.php index 6b87ed27f..9b2aa025e 100644 --- a/lib/router.php +++ b/lib/router.php @@ -140,11 +140,13 @@ class Router // settings - foreach (array('profile', 'avatar', 'password', 'im', + foreach (array('profile', 'avatar', 'password', 'im', 'application', 'email', 'sms', 'userdesign', 'other') as $s) { $m->connect('settings/'.$s, array('action' => $s.'settings')); } + $m->connect('settings/oauthclients', array('action' => 'oauthclients')); + // search foreach (array('group', 'people', 'notice') as $s) { -- cgit v1.2.3-54-g00ecf From 9d958fd539ecb74356013c11a2fd2c4c8b6a0ed1 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 13 Nov 2009 19:02:18 -0800 Subject: Reorganized the OAuth app URLs and more work on the register app workflow --- actions/applicationsettings.php | 135 ---------------------- actions/apps.php | 108 ++++++++++++++++++ actions/newapplication.php | 202 ++++++++++++++++++++++++++++++++ actions/oauthclients.php | 108 ------------------ actions/oauthconnectionssettings.php | 135 ++++++++++++++++++++++ lib/applicationeditform.php | 215 +++++++++++++++++++++++++++++++++++ lib/connectsettingsaction.php | 8 +- lib/router.php | 15 ++- 8 files changed, 675 insertions(+), 251 deletions(-) delete mode 100644 actions/applicationsettings.php create mode 100644 actions/apps.php create mode 100644 actions/newapplication.php delete mode 100644 actions/oauthclients.php create mode 100644 actions/oauthconnectionssettings.php create mode 100644 lib/applicationeditform.php diff --git a/actions/applicationsettings.php b/actions/applicationsettings.php deleted file mode 100644 index 16c571fee..000000000 --- a/actions/applicationsettings.php +++ /dev/null @@ -1,135 +0,0 @@ -. - * - * @category Settings - * @package StatusNet - * @author Zach Copley - * @copyright 2008-2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR . '/lib/connectsettingsaction.php'; -require_once INSTALLDIR . '/lib/applicationlist.php'; - -/** - * Show connected OAuth applications - * - * @category Settings - * @package StatusNet - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * - * @see SettingsAction - */ - -class ApplicationSettingsAction extends ConnectSettingsAction -{ - /** - * Title of the page - * - * @return string Title of the page - */ - - function title() - { - return _('Connected Applications'); - } - - /** - * Instructions for use - * - * @return instructions for use - */ - - function getInstructions() - { - return _('You have allowed the following applications to access you account.'); - } - - /** - * Content area of the page - * - * @return void - */ - - function showContent() - { - $user = common_current_user(); - $profile = $user->getProfile(); - - $offset = ($this->page - 1) * APPS_PER_PAGE; - $limit = APPS_PER_PAGE + 1; - - $application = $profile->getApplications($offset, $limit); - - if ($application) { - $al = new ApplicationList($application, $this->user, $this); - $cnt = $al->show(); - if (0 == $cnt) { - $this->showEmptyListMessage(); - } - } - - $this->pagination($this->page > 1, $cnt > APPS_PER_PAGE, - $this->page, 'applicationsettings', - array('nickname' => $this->user->nickname)); - } - - /** - * Handle posts to this form - * - * Based on the button that was pressed, muxes out to other functions - * to do the actual task requested. - * - * All sub-functions reload the form with a message -- success or failure. - * - * @return void - */ - - function handlePost() - { - // CSRF protection - - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - - } - - function showEmptyListMessage() - { - $message = sprintf(_('You have not authorized any applications to use your account.')); - - $this->elementStart('div', 'guide'); - $this->raw(common_markup_to_html($message)); - $this->elementEnd('div'); - } - -} diff --git a/actions/apps.php b/actions/apps.php new file mode 100644 index 000000000..d4cea1e3e --- /dev/null +++ b/actions/apps.php @@ -0,0 +1,108 @@ +. + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/connectsettingsaction.php'; + +/** + * Show a user's registered OAuth applications + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see SettingsAction + */ + +class AppsAction extends ConnectSettingsAction +{ + /** + * Title of the page + * + * @return string Title of the page + */ + + function title() + { + return _('OAuth applications'); + } + + /** + * Instructions for use + * + * @return instructions for use + */ + + function getInstructions() + { + return _('Applications you have registered'); + } + + /** + * Content area of the page + * + * @return void + */ + + function showContent() + { + $user = common_current_user(); + + } + + /** + * Handle posts to this form + * + * Based on the button that was pressed, muxes out to other functions + * to do the actual task requested. + * + * All sub-functions reload the form with a message -- success or failure. + * + * @return void + */ + + function handlePost() + { + // CSRF protection + + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + } + +} diff --git a/actions/newapplication.php b/actions/newapplication.php new file mode 100644 index 000000000..a78a856b1 --- /dev/null +++ b/actions/newapplication.php @@ -0,0 +1,202 @@ +. + * + * @category Applications + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Add a new application + * + * This is the form for adding a new application + * + * @category Application + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class NewApplicationAction extends Action +{ + var $msg; + + function title() + { + return _('New Application'); + } + + /** + * Prepare to run + */ + + function prepare($args) + { + parent::prepare($args); + + if (!common_logged_in()) { + $this->clientError(_('You must be logged in to create a group.')); + return false; + } + + return true; + } + + /** + * Handle the request + * + * On GET, show the form. On POST, try to save the group. + * + * @param array $args unused + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $this->trySave(); + } else { + $this->showForm(); + } + } + + function showForm($msg=null) + { + $this->msg = $msg; + $this->showPage(); + } + + function showContent() + { + $form = new ApplicationEditForm($this); + $form->show(); + } + + function showPageNotice() + { + if ($this->msg) { + $this->element('p', 'error', $this->msg); + } else { + $this->element('p', 'instructions', + _('Use this form to register a new application.')); + } + } + + function trySave() + { + $name = $this->trimmed('name'); + $description = $this->trimmed('description'); + $source_url = $this->trimmed('source_url'); + $organization = $this->trimmed('organization'); + $homepage = $this->trimmed('application'); + $callback_url = $this->trimmed('callback_url'); + $this->type = $this->trimmed('type'); + $this->access_type = $this->trimmed('access_type'); + + if (!is_null($name) && mb_strlen($name) > 255) { + $this->showForm(_('Name is too long (max 255 chars).')); + return; + } else if (User_group::descriptionTooLong($description)) { + $this->showForm(sprintf( + _('description is too long (max %d chars).'), + Oauth_application::maxDescription())); + return; + } elseif (!is_null($source_url) + && (strlen($source_url) > 0) + && !Validate::uri( + $source_url, + array('allowed_schemes' => array('http', 'https')) + ) + ) + { + $this->showForm(_('Source URL is not valid.')); + return; + } elseif (!is_null($homepage) + && (strlen($homepage) > 0) + && !Validate::uri( + $homepage, + array('allowed_schemes' => array('http', 'https')) + ) + ) + { + $this->showForm(_('Homepage is not a valid URL.')); + return; + } elseif (!is_null($callback_url) + && (strlen($callback_url) > 0) + && !Validate::uri( + $source_url, + array('allowed_schemes' => array('http', 'https')) + ) + ) + { + $this->showForm(_('Callback URL is not valid.')); + return; + } + + $cur = common_current_user(); + + // Checked in prepare() above + + assert(!is_null($cur)); + + $app = new Oauth_application(); + + $app->query('BEGIN'); + + $app->name = $name; + $app->owner = $cur->id; + $app->description = $description; + $app->source_url = $souce_url; + $app->organization = $organization; + $app->homepage = $homepage; + $app->callback_url = $callback_url; + $app->type = $type; + $app->access_type = $access_type; + + // generate consumer key and secret + + $app->created = common_sql_now(); + + $result = $app->insert(); + + if (!$result) { + common_log_db_error($group, 'INSERT', __FILE__); + $this->serverError(_('Could not create application.')); + } + + $group->query('COMMIT'); + + common_redirect($group->homeUrl(), 303); + + } + +} + diff --git a/actions/oauthclients.php b/actions/oauthclients.php deleted file mode 100644 index 9a29e158e..000000000 --- a/actions/oauthclients.php +++ /dev/null @@ -1,108 +0,0 @@ -. - * - * @category Settings - * @package StatusNet - * @author Zach Copley - * @copyright 2008-2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR . '/lib/connectsettingsaction.php'; - -/** - * Show a user's registered OAuth applications - * - * @category Settings - * @package StatusNet - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * - * @see SettingsAction - */ - -class OauthClientsAction extends ConnectSettingsAction -{ - /** - * Title of the page - * - * @return string Title of the page - */ - - function title() - { - return _('Applications using %%site_name%%'); - } - - /** - * Instructions for use - * - * @return instructions for use - */ - - function getInstructions() - { - return _('Applications you have registered'); - } - - /** - * Content area of the page - * - * @return void - */ - - function showContent() - { - $user = common_current_user(); - - } - - /** - * Handle posts to this form - * - * Based on the button that was pressed, muxes out to other functions - * to do the actual task requested. - * - * All sub-functions reload the form with a message -- success or failure. - * - * @return void - */ - - function handlePost() - { - // CSRF protection - - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - - } - -} diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php new file mode 100644 index 000000000..6ec9f7027 --- /dev/null +++ b/actions/oauthconnectionssettings.php @@ -0,0 +1,135 @@ +. + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/connectsettingsaction.php'; +require_once INSTALLDIR . '/lib/applicationlist.php'; + +/** + * Show connected OAuth applications + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see SettingsAction + */ + +class OauthconnectionssettingsAction extends ConnectSettingsAction +{ + /** + * Title of the page + * + * @return string Title of the page + */ + + function title() + { + return _('Connected Applications'); + } + + /** + * Instructions for use + * + * @return instructions for use + */ + + function getInstructions() + { + return _('You have allowed the following applications to access you account.'); + } + + /** + * Content area of the page + * + * @return void + */ + + function showContent() + { + $user = common_current_user(); + $profile = $user->getProfile(); + + $offset = ($this->page - 1) * APPS_PER_PAGE; + $limit = APPS_PER_PAGE + 1; + + $application = $profile->getApplications($offset, $limit); + + if ($application) { + $al = new ApplicationList($application, $this->user, $this); + $cnt = $al->show(); + if (0 == $cnt) { + $this->showEmptyListMessage(); + } + } + + $this->pagination($this->page > 1, $cnt > APPS_PER_PAGE, + $this->page, 'connectionssettings', + array('nickname' => $this->user->nickname)); + } + + /** + * Handle posts to this form + * + * Based on the button that was pressed, muxes out to other functions + * to do the actual task requested. + * + * All sub-functions reload the form with a message -- success or failure. + * + * @return void + */ + + function handlePost() + { + // CSRF protection + + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + } + + function showEmptyListMessage() + { + $message = sprintf(_('You have not authorized any applications to use your account.')); + + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); + } + +} diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php new file mode 100644 index 000000000..3fd45876a --- /dev/null +++ b/lib/applicationeditform.php @@ -0,0 +1,215 @@ +. + * + * @category Form + * @package StatusNet + * @author Zach Copley + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/form.php'; + +/** + * Form for editing an application + * + * @category Form + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + */ + +class ApplicationEditForm extends Form +{ + /** + * group for user to join + */ + + var $application = null; + + /** + * Constructor + * + * @param Action $out output channel + * @param User_group $group group to join + */ + + function __construct($out=null, $application=null) + { + parent::__construct($out); + + $this->application = $application; + } + + /** + * ID of the form + * + * @return string ID of the form + */ + + function id() + { + if ($this->application) { + return 'form_application_edit-' . $this->application->id; + } else { + return 'form_application_add'; + } + } + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + return 'form_settings'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + if ($this->application) { + return common_local_url('editapplication', + array('id' => $this->application->id)); + } else { + return common_local_url('newapplication'); + } + } + + /** + * Name of the form + * + * @return void + */ + + function formLegend() + { + $this->out->element('legend', null, _('Register a new application')); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + if ($this->application) { + $id = $this->application->id; + $name = $this->application->name; + $description = $this->application->description; + $source_url = $this->application->source_url; + $organization = $this->application->organization; + $homepage = $this->application->homepage; + $callback_url = $this->application->callback_url; + $this->type = $this->application->type; + $this->access_type = $this->application->access_type; + } else { + $id = ''; + $name = ''; + $description = ''; + $source_url = ''; + $organization = ''; + $homepage = ''; + $callback_url = ''; + $this->type = ''; + $this->access_type = ''; + } + + $this->out->elementStart('ul', 'form_data'); + $this->out->elementStart('li'); + + $this->out->hidden('application_id', $id); + $this->out->input('name', _('Name'), + ($this->out->arg('name')) ? $this->out->arg('name') : $name); + + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('description', _('Description'), + ($this->out->arg('Description')) ? $this->out->arg('discription') : $description); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('source_url', _('Source URL'), + ($this->out->arg('source_url')) ? $this->out->arg('source_url') : $source_url, + _('URL of the homepage of this application')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('Organization', _('Organization'), + ($this->out->arg('organization')) ? $this->out->arg('organization') : $orgranization, + _('Organization responsible for this application')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('homepage', _('Homepage'), + ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage, + _('URL of the homepage of the organization')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('callback_url', ('Callback URL'), + ($this->out->arg('callback_url')) ? $this->out->arg('callback_url') : $callback_url, + _('URL to redirect to after authentication')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('type', _('Application type'), + ($this->out->arg('type')) ? $this->out->arg('type') : $type, + _('Type of application, browser or desktop')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('access_type', _('Default access'), + ($this->out->arg('access_type')) ? $this->out->arg('access_type') : $access_type, + _('Default access for this application: read-write, or read-only')); + $this->out->elementEnd('li'); + + $this->out->elementEnd('ul'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Save')); + } +} diff --git a/lib/connectsettingsaction.php b/lib/connectsettingsaction.php index 4b5059540..b9c14799e 100644 --- a/lib/connectsettingsaction.php +++ b/lib/connectsettingsaction.php @@ -115,9 +115,11 @@ class ConnectSettingsNav extends Widget array(_('SMS'), _('Updates by SMS')); } - - $menu['applicationsettings'] = array(_('Applications'), - _('OAuth connected applications')); + + $menu['oauthconnectionssettings'] = array( + _('Connections'), + _('Authorized connected applications') + ); foreach ($menu as $menuaction => $menudesc) { $this->action->menuItem(common_local_url($menuaction), diff --git a/lib/router.php b/lib/router.php index 9b2aa025e..7b65ae215 100644 --- a/lib/router.php +++ b/lib/router.php @@ -140,13 +140,11 @@ class Router // settings - foreach (array('profile', 'avatar', 'password', 'im', 'application', + foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections', 'email', 'sms', 'userdesign', 'other') as $s) { $m->connect('settings/'.$s, array('action' => $s.'settings')); } - - $m->connect('settings/oauthclients', array('action' => 'oauthclients')); - + // search foreach (array('group', 'people', 'notice') as $s) { @@ -636,12 +634,19 @@ class Router // user stuff foreach (array('subscriptions', 'subscribers', - 'nudge', 'all', 'foaf', 'xrds', + 'nudge', 'all', 'foaf', 'xrds', 'apps', 'replies', 'inbox', 'outbox', 'microsummary') as $a) { $m->connect(':nickname/'.$a, array('action' => $a), array('nickname' => '[a-zA-Z0-9]{1,64}')); } + + $m->connect('apps/new', array('action' => 'newapplication')); + + $m->connect(':nickname/apps/edit', + array('action' => 'editapplication'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}') + ); foreach (array('subscriptions', 'subscribers') as $a) { $m->connect(':nickname/'.$a.'/:tag', -- cgit v1.2.3-54-g00ecf From 035c475b45959057099c503d2cdcff8c8145e198 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 13 Nov 2009 19:10:38 -0800 Subject: It might help if I checkd in statusnet.ini. --- classes/statusnet.ini | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 6ce4495be..0cbe60a5a 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -39,6 +39,7 @@ code = K [consumer] consumer_key = 130 +consumer_secret = 130 seed = 130 created = 142 modified = 384 @@ -348,6 +349,35 @@ created = 142 tag = K notice_id = K +[oauth_application] +id = 129 +owner = 129 +consumer_key = 130 +name = 130 +description = 2 +icon = 130 +source_url = 2 +organization = 2 +homepage = 2 +callback_url = 130 +type = 17 +access_type = 17 +created = 142 +modified = 384 + +[oauth_application__keys] +id = N + +[oauth_application_user] +profile_id = 129 +application_id = 129 +access_type = 17 +created = 142 + +[oauth_application_user__keys] +profile_id = K +application_id = K + [profile] id = 129 nickname = 130 -- cgit v1.2.3-54-g00ecf From 3c2b05d222a55cd1e148f3f887bf55e924898f1b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 16 Nov 2009 16:58:49 -0800 Subject: Workflow for registering new OAuth apps pretty much done. --- actions/apps.php | 63 +++++++- actions/editapplication.php | 246 ++++++++++++++++++++++++++++ actions/newapplication.php | 133 ++++++++++----- actions/oauthconnectionssettings.php | 13 ++ actions/showapplication.php | 306 +++++++++++++++++++++++++++++++++++ classes/Consumer.php | 16 +- classes/Oauth_application.php | 44 +++++ db/statusnet.sql | 2 +- lib/applicationeditform.php | 135 +++++++++++++--- lib/applicationlist.php | 46 ++++-- lib/default.php | 2 + lib/router.php | 25 ++- 12 files changed, 949 insertions(+), 82 deletions(-) create mode 100644 actions/editapplication.php create mode 100644 actions/showapplication.php diff --git a/actions/apps.php b/actions/apps.php index d4cea1e3e..e6500599f 100644 --- a/actions/apps.php +++ b/actions/apps.php @@ -31,7 +31,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR . '/lib/connectsettingsaction.php'; +require_once INSTALLDIR . '/lib/settingsaction.php'; +require_once INSTALLDIR . '/lib/applicationlist.php'; /** * Show a user's registered OAuth applications @@ -45,8 +46,23 @@ require_once INSTALLDIR . '/lib/connectsettingsaction.php'; * @see SettingsAction */ -class AppsAction extends ConnectSettingsAction +class AppsAction extends SettingsAction { + var $page = 0; + + function prepare($args) + { + parent::prepare($args); + $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1; + + if (!common_logged_in()) { + $this->clientError(_('You must be logged in to list your applications.')); + return false; + } + + return true; + } + /** * Title of the page * @@ -79,6 +95,49 @@ class AppsAction extends ConnectSettingsAction { $user = common_current_user(); + $offset = ($this->page - 1) * APPS_PER_PAGE; + $limit = APPS_PER_PAGE + 1; + + $application = new Oauth_application(); + $application->owner = $user->id; + $application->limit($offset, $limit); + $application->orderBy('created DESC'); + $application->find(); + + $cnt = 0; + + if ($application) { + $al = new ApplicationList($application, $user, $this); + $cnt = $al->show(); + if (0 == $cnt) { + $this->showEmptyListMessage(); + } + } + + $this->element('a', + array('href' => common_local_url( + 'newapplication', + array('nickname' => $user->nickname) + ) + ), + 'Register a new application »'); + + $this->pagination( + $this->page > 1, + $cnt > APPS_PER_PAGE, + $this->page, + 'apps', + array('nickname' => $user->nickname) + ); + } + + function showEmptyListMessage() + { + $message = sprintf(_('You have not registered any applications yet.')); + + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); } /** diff --git a/actions/editapplication.php b/actions/editapplication.php new file mode 100644 index 000000000..3af482844 --- /dev/null +++ b/actions/editapplication.php @@ -0,0 +1,246 @@ +. + * + * @category Applications + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Edit the details of an OAuth application + * + * This is the form for editing an application + * + * @category Application + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class EditApplicationAction extends OwnerDesignAction +{ + var $msg = null; + + var $app = null; + + function title() + { + return _('Edit Application'); + } + + /** + * Prepare to run + */ + + function prepare($args) + { + parent::prepare($args); + + if (!common_logged_in()) { + $this->clientError(_('You must be logged in to edit an application.')); + return false; + } + + $id = (int)$this->arg('id'); + $this->app = Oauth_application::staticGet($id); + + if (!$this->app) { + $this->clientError(_('No such application.')); + return false; + } + + return true; + } + + /** + * Handle the request + * + * On GET, show the form. On POST, try to save the group. + * + * @param array $args unused + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } + + $cur = common_current_user(); + + if ($this->arg('cancel')) { + common_redirect(common_local_url('showapplication', + array( + 'nickname' => $cur->nickname, + 'id' => $this->app->id) + ), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } + } else { + $this->showForm(); + } + } + + function showForm($msg=null) + { + $this->msg = $msg; + $this->showPage(); + } + + function showContent() + { + $form = new ApplicationEditForm($this, $this->app); + $form->show(); + } + + function showPageNotice() + { + if (!empty($this->msg)) { + $this->element('p', 'error', $this->msg); + } else { + $this->element('p', 'instructions', + _('Use this form to edit your application.')); + } + } + + function trySave() + { + $name = $this->trimmed('name'); + $description = $this->trimmed('description'); + $source_url = $this->trimmed('source_url'); + $organization = $this->trimmed('organization'); + $homepage = $this->trimmed('homepage'); + $callback_url = $this->trimmed('callback_url'); + $type = $this->arg('app_type'); + $access_type = $this->arg('access_type'); + + if (empty($name)) { + $this->showForm(_('Name is required.')); + return; + } elseif (mb_strlen($name) > 255) { + $this->showForm(_('Name is too long (max 255 chars).')); + return; + } elseif (empty($description)) { + $this->showForm(_('Description is required.')); + return; + } elseif (Oauth_application::descriptionTooLong($description)) { + $this->showForm(sprintf( + _('Description is too long (max %d chars).'), + Oauth_application::maxDescription())); + return; + } elseif (empty($source_url)) { + $this->showForm(_('Source URL is required.')); + return; + } elseif ((strlen($source_url) > 0) + && !Validate::uri( + $source_url, + array('allowed_schemes' => array('http', 'https')) + ) + ) + { + $this->showForm(_('Source URL is not valid.')); + return; + } elseif (empty($organization)) { + $this->showForm(_('Organization is required.')); + return; + } elseif (mb_strlen($organization) > 255) { + $this->showForm(_('Organization is too long (max 255 chars).')); + return; + } elseif (empty($homepage)) { + $this->showForm(_('Organization homepage is required.')); + return; + } elseif ((strlen($homepage) > 0) + && !Validate::uri( + $homepage, + array('allowed_schemes' => array('http', 'https')) + ) + ) + { + $this->showForm(_('Homepage is not a valid URL.')); + return; + } elseif (empty($callback_url)) { + $this->showForm(_('Callback is required.')); + return; + } elseif (strlen($callback_url) > 0 + && !Validate::uri( + $source_url, + array('allowed_schemes' => array('http', 'https')) + ) + ) + { + $this->showForm(_('Callback URL is not valid.')); + return; + } + + $cur = common_current_user(); + + // Checked in prepare() above + + assert(!is_null($cur)); + + $orig = clone($this->app); + + $this->app->name = $name; + $this->app->description = $description; + $this->app->source_url = $source_url; + $this->app->organization = $organization; + $this->app->homepage = $homepage; + $this->app->callback_url = $callback_url; + $this->app->type = $type; + + if ($access_type == 'r') { + $this->app->setAccessFlags(true, false); + } else { + $this->app->setAccessFlags(true, true); + } + + $result = $this->app->update($orig); + + if (!$result) { + common_log_db_error($app, 'UPDATE', __FILE__); + $this->serverError(_('Could not update application.')); + } + + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } + +} + diff --git a/actions/newapplication.php b/actions/newapplication.php index a78a856b1..9d8635270 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -43,7 +43,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { * @link http://status.net/ */ -class NewApplicationAction extends Action +class NewApplicationAction extends OwnerDesignAction { var $msg; @@ -61,7 +61,7 @@ class NewApplicationAction extends Action parent::prepare($args); if (!common_logged_in()) { - $this->clientError(_('You must be logged in to create a group.')); + $this->clientError(_('You must be logged in to register an application.')); return false; } @@ -81,8 +81,19 @@ class NewApplicationAction extends Action function handle($args) { parent::handle($args); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $this->trySave(); + + $cur = common_current_user(); + + if ($this->arg('cancel')) { + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } } else { $this->showForm(); } @@ -112,55 +123,73 @@ class NewApplicationAction extends Action function trySave() { - $name = $this->trimmed('name'); - $description = $this->trimmed('description'); - $source_url = $this->trimmed('source_url'); - $organization = $this->trimmed('organization'); - $homepage = $this->trimmed('application'); - $callback_url = $this->trimmed('callback_url'); - $this->type = $this->trimmed('type'); - $this->access_type = $this->trimmed('access_type'); - - if (!is_null($name) && mb_strlen($name) > 255) { + $name = $this->trimmed('name'); + $description = $this->trimmed('description'); + $source_url = $this->trimmed('source_url'); + $organization = $this->trimmed('organization'); + $homepage = $this->trimmed('homepage'); + $callback_url = $this->trimmed('callback_url'); + $type = $this->arg('app_type'); + $access_type = $this->arg('access_type'); + + if (empty($name)) { + $this->showForm(_('Name is required.')); + return; + } elseif (mb_strlen($name) > 255) { $this->showForm(_('Name is too long (max 255 chars).')); return; - } else if (User_group::descriptionTooLong($description)) { + } elseif (empty($description)) { + $this->showForm(_('Description is required.')); + return; + } elseif (Oauth_application::descriptionTooLong($description)) { $this->showForm(sprintf( - _('description is too long (max %d chars).'), + _('Description is too long (max %d chars).'), Oauth_application::maxDescription())); return; - } elseif (!is_null($source_url) - && (strlen($source_url) > 0) + } elseif (empty($source_url)) { + $this->showForm(_('Source URL is required.')); + return; + } elseif ((strlen($source_url) > 0) && !Validate::uri( $source_url, array('allowed_schemes' => array('http', 'https')) ) - ) + ) { $this->showForm(_('Source URL is not valid.')); return; - } elseif (!is_null($homepage) - && (strlen($homepage) > 0) + } elseif (empty($organization)) { + $this->showForm(_('Organization is required.')); + return; + } elseif (mb_strlen($organization) > 255) { + $this->showForm(_('Organization is too long (max 255 chars).')); + return; + } elseif (empty($homepage)) { + $this->showForm(_('Organization homepage is required.')); + return; + } elseif ((strlen($homepage) > 0) && !Validate::uri( $homepage, array('allowed_schemes' => array('http', 'https')) ) - ) + ) { $this->showForm(_('Homepage is not a valid URL.')); - return; - } elseif (!is_null($callback_url) - && (strlen($callback_url) > 0) + return; + } elseif (empty($callback_url)) { + $this->showForm(_('Callback is required.')); + return; + } elseif (strlen($callback_url) > 0 && !Validate::uri( $source_url, array('allowed_schemes' => array('http', 'https')) ) - ) + ) { $this->showForm(_('Callback URL is not valid.')); return; } - + $cur = common_current_user(); // Checked in prepare() above @@ -171,31 +200,53 @@ class NewApplicationAction extends Action $app->query('BEGIN'); - $app->name = $name; - $app->owner = $cur->id; - $app->description = $description; - $app->source_url = $souce_url; + $app->name = $name; + $app->owner = $cur->id; + $app->description = $description; + $app->source_url = $source_url; $app->organization = $organization; - $app->homepage = $homepage; + $app->homepage = $homepage; $app->callback_url = $callback_url; - $app->type = $type; - $app->access_type = $access_type; - + $app->type = $type; + + // Yeah, I dunno why I chose bit flags. I guess so I could + // copy this value directly to Oauth_application_user + // access_type which I think does need bit flags -- Z + + if ($access_type == 'r') { + $app->setAccessFlags(true, false); + } else { + $app->setAccessFlags(true, true); + } + + $app->created = common_sql_now(); + // generate consumer key and secret - - $app->created = common_sql_now(); + + $consumer = Consumer::generateNew(); + + $result = $consumer->insert(); + + if (!$result) { + common_log_db_error($consumer, 'INSERT', __FILE__); + $this->serverError(_('Could not create application.')); + } + + $app->consumer_key = $consumer->consumer_key; $result = $app->insert(); if (!$result) { - common_log_db_error($group, 'INSERT', __FILE__); + common_log_db_error($app, 'INSERT', __FILE__); $this->serverError(_('Could not create application.')); + $app->query('ROLLBACK'); } - - $group->query('COMMIT'); - common_redirect($group->homeUrl(), 303); - + $app->query('COMMIT'); + + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } } diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index 6ec9f7027..e4b5af158 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -132,4 +132,17 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction $this->elementEnd('div'); } + function showSections() + { + $cur = common_current_user(); + + $this->element('h2', null, 'Developers'); + $this->elementStart('p'); + $this->raw(_('Developers can edit the registration settings for their applications ')); + $this->element('a', + array('href' => common_local_url('apps', array('nickname' => $cur->nickname))), + 'here.'); + $this->elementEnd('p'); + } + } diff --git a/actions/showapplication.php b/actions/showapplication.php new file mode 100644 index 000000000..6b8eff4a6 --- /dev/null +++ b/actions/showapplication.php @@ -0,0 +1,306 @@ +. + * + * @category Application + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Show an OAuth application + * + * @category Application + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ShowApplicationAction extends OwnerDesignAction +{ + /** + * Application to show + */ + + var $application = null; + + /** + * User who owns the app + */ + + var $owner = null; + + + var $msg = null; + + var $success = null; + + /** + * Load attributes based on database arguments + * + * Loads all the DB stuff + * + * @param array $args $_REQUEST array + * + * @return success flag + */ + + function prepare($args) + { + parent::prepare($args); + + $id = (int)$this->arg('id'); + + $this->application = Oauth_application::staticGet($id); + $this->owner = User::staticGet($this->application->owner); + + if (!common_logged_in()) { + $this->clientError(_('You must be logged in to view an application.')); + return false; + } + + if (empty($this->application)) { + $this->clientError(_('No such application.'), 404); + return false; + } + + $cur = common_current_user(); + + if ($cur->id != $this->owner->id) { + $this->clientError(_('You are not the owner of this application.'), 401); + } + + return true; + } + + /** + * Handle the request + * + * Shows info about the app + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } + + if ($this->arg('reset')) { + $this->resetKey(); + } + } else { + $this->showPage(); + } + } + + /** + * Title of the page + * + * @return string title of the page + */ + + function title() + { + if (!empty($this->application->name)) { + return 'Application: ' . $this->application->name; + } + } + + function showPageNotice() + { + if (!empty($this->msg)) { + $this->element('div', ($this->success) ? 'success' : 'error', $this->msg); + } + } + + function showContent() + { + + $cur = common_current_user(); + + $this->elementStart('div', 'entity_actions'); + + $this->element('a', + array('href' => + common_local_url( + 'editapplication', + array( + 'nickname' => $this->owner->nickname, + 'id' => $this->application->id + ) + ) + ), 'Edit application'); + + $this->elementStart('form', array( + 'id' => 'forma_reset_key', + 'class' => 'form_reset_key', + 'method' => 'POST', + 'action' => common_local_url('showapplication', + array('nickname' => $cur->nickname, + 'id' => $this->application->id)))); + + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->submit('reset', _('Reset Consumer key/secret')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + + $this->elementEnd('div'); + + $consumer = $this->application->getConsumer(); + + $this->elementStart('div', 'entity-application'); + + $this->elementStart('ul', 'entity_application_details'); + + $this->elementStart('li', 'entity_application_name'); + $this->element('span', array('class' => 'big'), $this->application->name); + $this->raw(sprintf(_(' by %1$s'), $this->application->organization)); + $this->elementEnd('li'); + + $this->element('li', 'entity_application_description', $this->application->description); + + $this->elementStart('li', 'entity_application_statistics'); + + $defaultAccess = ($this->application->access_type & Oauth_application::$writeAccess) + ? 'read-write' : 'read-only'; + $profile = Profile::staticGet($this->application->owner); + $userCnt = 0; // XXX: count how many users use the app + + $this->raw(sprintf( + _('Created by %1$s - %2$s access by default - %3$d users.'), + $profile->getBestName(), + $defaultAccess, + $userCnt + )); + + $this->elementEnd('li'); + + $this->elementEnd('ul'); + + $this->elementStart('dl', 'entity_consumer_key'); + $this->element('dt', null, _('Consumer key')); + $this->element('dd', 'label', $consumer->consumer_key); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_consumer_secret'); + $this->element('dt', null, _('Consumer secret')); + $this->element('dd', 'label', $consumer->consumer_secret); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_request_token_url'); + $this->element('dt', null, _('Request token URL')); + $this->element('dd', 'label', common_local_url('oauthrequesttoken')); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_access_token_url'); + $this->element('dt', null, _('Access token URL')); + $this->element('dd', 'label', common_local_url('oauthaccesstoken')); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_authorize_url'); + $this->element('dt', null, _('Authorize URL')); + $this->element('dd', 'label', common_local_url('oauthauthorize')); + $this->elementEnd('dl'); + + $this->element('p', 'oauth-signature-note', + '*We support hmac-sha1 signatures. We do not support the plaintext signature method.'); + + $this->elementEnd('div'); + + $this->elementStart('div', 'entity-list-apps'); + $this->element('a', + array( + 'href' => common_local_url( + 'apps', + array('nickname' => $this->owner->nickname) + ) + ), + 'View your applications'); + $this->elementEnd('div'); + } + + function resetKey() + { + $this->application->query('BEGIN'); + + $consumer = $this->application->getConsumer(); + $result = $consumer->delete(); + + if (!$result) { + common_log_db_error($consumer, 'DELETE', __FILE__); + $this->success = false; + $this->msg = ('Unable to reset consumer key and secret.'); + $this->showPage(); + return; + } + + $consumer = Consumer::generateNew(); + + $result = $consumer->insert(); + + if (!$result) { + common_log_db_error($consumer, 'INSERT', __FILE__); + $this->application->query('ROLLBACK'); + $this->success = false; + $this->msg = ('Unable to reset consumer key and secret.'); + $this->showPage(); + return; + } + + $orig = clone($this->application); + $this->application->consumer_key = $consumer->consumer_key; + $result = $this->application->update($orig); + + if (!$result) { + common_log_db_error($application, 'UPDATE', __FILE__); + $this->application->query('ROLLBACK'); + $this->success = false; + $this->msg = ('Unable to reset consumer key and secret.'); + $this->showPage(); + return; + } + + $this->application->query('COMMIT'); + + $this->success = true; + $this->msg = ('Consumer key and secret reset.'); + $this->showPage(); + } + +} + diff --git a/classes/Consumer.php b/classes/Consumer.php index d17f183a8..ad64a8491 100644 --- a/classes/Consumer.php +++ b/classes/Consumer.php @@ -4,7 +4,7 @@ */ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Consumer extends Memcached_DataObject +class Consumer extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -22,4 +22,18 @@ class Consumer extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + + static function generateNew() + { + $cons = new Consumer(); + $rand = common_good_rand(16); + + $cons->seed = $rand; + $cons->consumer_key = md5(time() + $rand); + $cons->consumer_secret = md5(md5(time() + time() + $rand)); + $cons->created = common_sql_now(); + + return $cons; + } + } diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php index e2862bf97..ef1bbf6d9 100644 --- a/classes/Oauth_application.php +++ b/classes/Oauth_application.php @@ -31,4 +31,48 @@ class Oauth_application extends Memcached_DataObject } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + + // Bit flags + public static $readAccess = 1; + public static $writeAccess = 2; + + public static $browser = 1; + public static $desktop = 2; + + function getConsumer() + { + return Consumer::staticGet('consumer_key', $this->consumer_key); + } + + static function maxDesc() + { + $desclimit = common_config('application', 'desclimit'); + // null => use global limit (distinct from 0!) + if (is_null($desclimit)) { + $desclimit = common_config('site', 'textlimit'); + } + return $desclimit; + } + + static function descriptionTooLong($desc) + { + $desclimit = self::maxDesc(); + return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit)); + } + + function setAccessFlags($read, $write) + { + if ($read) { + $this->access_type |= self::$readAccess; + } else { + $this->access_type &= ~self::$readAccess; + } + + if ($write) { + $this->access_type |= self::$writeAccess; + } else { + $this->access_type &= ~self::$writeAccess; + } + } + } diff --git a/db/statusnet.sql b/db/statusnet.sql index 03e6115e5..a2740b60c 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -219,7 +219,7 @@ create table oauth_application ( organization varchar(255) comment 'name of the organization running the application', homepage varchar(255) comment 'homepage for the organization', callback_url varchar(255) not null comment 'url to redirect to after authentication', - type tinyint default 0 comment 'type of app, 0 = browser, 1 = desktop', + type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop', access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified' diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index 3fd45876a..ed187ba0b 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -100,11 +100,16 @@ class ApplicationEditForm extends Form function action() { - if ($this->application) { + $cur = common_current_user(); + + if (!empty($this->application)) { return common_local_url('editapplication', - array('id' => $this->application->id)); + array('id' => $this->application->id, + 'nickname' => $cur->nickname) + ); } else { - return common_local_url('newapplication'); + return common_local_url('newapplication', + array('nickname' => $cur->nickname)); } } @@ -116,7 +121,7 @@ class ApplicationEditForm extends Form function formLegend() { - $this->out->element('legend', null, _('Register a new application')); + $this->out->element('legend', null, _('Edit application')); } /** @@ -130,7 +135,7 @@ class ApplicationEditForm extends Form if ($this->application) { $id = $this->application->id; $name = $this->application->name; - $description = $this->application->description; + $description = $this->application->description; $source_url = $this->application->source_url; $organization = $this->application->organization; $homepage = $this->application->homepage; @@ -151,34 +156,46 @@ class ApplicationEditForm extends Form $this->out->elementStart('ul', 'form_data'); $this->out->elementStart('li'); - + $this->out->hidden('application_id', $id); + $this->out->hidden('token', common_session_token()); + $this->out->input('name', _('Name'), ($this->out->arg('name')) ? $this->out->arg('name') : $name); - + $this->out->elementEnd('li'); - + $this->out->elementStart('li'); - $this->out->input('description', _('Description'), - ($this->out->arg('Description')) ? $this->out->arg('discription') : $description); + + $maxDesc = Oauth_application::maxDesc(); + if ($maxDesc > 0) { + $descInstr = sprintf(_('Describe your application in %d chars'), + $maxDesc); + } else { + $descInstr = _('Describe your application'); + } + $this->out->textarea('description', _('Description'), + ($this->out->arg('description')) ? $this->out->arg('description') : $description, + $descInstr); + $this->out->elementEnd('li'); - + $this->out->elementStart('li'); $this->out->input('source_url', _('Source URL'), ($this->out->arg('source_url')) ? $this->out->arg('source_url') : $source_url, _('URL of the homepage of this application')); - $this->out->elementEnd('li'); + $this->out->elementEnd('li'); $this->out->elementStart('li'); - $this->out->input('Organization', _('Organization'), - ($this->out->arg('organization')) ? $this->out->arg('organization') : $orgranization, + $this->out->input('organization', _('Organization'), + ($this->out->arg('organization')) ? $this->out->arg('organization') : $organization, _('Organization responsible for this application')); $this->out->elementEnd('li'); $this->out->elementStart('li'); $this->out->input('homepage', _('Homepage'), ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage, - _('URL of the homepage of the organization')); + _('URL for the homepage of the organization')); $this->out->elementEnd('li'); $this->out->elementStart('li'); @@ -188,17 +205,86 @@ class ApplicationEditForm extends Form $this->out->elementEnd('li'); $this->out->elementStart('li'); - $this->out->input('type', _('Application type'), - ($this->out->arg('type')) ? $this->out->arg('type') : $type, - _('Type of application, browser or desktop')); + + $attrs = array('name' => 'app_type', + 'type' => 'radio', + 'id' => 'app_type-browser', + 'class' => 'radio', + 'value' => Oauth_application::$browser); + + // Default to Browser + + if ($this->application->type == Oauth_application::$browser + || empty($this->applicaiton->type)) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + + $this->out->element('label', array('for' => 'app_type-browser', + 'class' => 'radio'), + _('Browser')); + + $attrs = array('name' => 'app_type', + 'type' => 'radio', + 'id' => 'app_type-dekstop', + 'class' => 'radio', + 'value' => Oauth_application::$desktop); + + if ($this->application->type == Oauth_application::$desktop) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + + $this->out->element('label', array('for' => 'app_type-desktop', + 'class' => 'radio'), + _('Desktop')); + $this->out->element('p', 'form_guide', _('Type of application, browser or desktop')); $this->out->elementEnd('li'); - + $this->out->elementStart('li'); - $this->out->input('access_type', _('Default access'), - ($this->out->arg('access_type')) ? $this->out->arg('access_type') : $access_type, - _('Default access for this application: read-write, or read-only')); + + $attrs = array('name' => 'default_access_type', + 'type' => 'radio', + 'id' => 'default_access_type-r', + 'class' => 'radio', + 'value' => 'r'); + + // default to read-only access + + if ($this->application->access_type & Oauth_application::$readAccess + || empty($this->application->access_type)) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + + $this->out->element('label', array('for' => 'default_access_type-ro', + 'class' => 'radio'), + _('Read-only')); + + $attrs = array('name' => 'default_access_type', + 'type' => 'radio', + 'id' => 'default_access_type-rw', + 'class' => 'radio', + 'value' => 'rw'); + + if ($this->application->access_type & Oauth_application::$readAccess + && $this->application->access_type & Oauth_application::$writeAccess + ) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + + $this->out->element('label', array('for' => 'default_access_type-rw', + 'class' => 'radio'), + _('Read-write')); + $this->out->element('p', 'form_guide', _('Default access for this application: read-only, or read-write')); + $this->out->elementEnd('li'); - + $this->out->elementEnd('ul'); } @@ -210,6 +296,7 @@ class ApplicationEditForm extends Form function formActions() { - $this->out->submit('submit', _('Save')); + $this->out->submit('save', _('Save')); + $this->out->submit('cancel', _('Cancel')); } } diff --git a/lib/applicationlist.php b/lib/applicationlist.php index fed784bb6..3141ea974 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -20,7 +20,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category Public + * @category Application * @package StatusNet * @author Zach Copley * @copyright 2008-2009 StatusNet, Inc. @@ -39,7 +39,7 @@ define('APPS_PER_PAGE', 20); /** * Widget to show a list of OAuth applications * - * @category Public + * @category Application * @package StatusNet * @author Zach Copley * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 @@ -50,10 +50,10 @@ class ApplicationList extends Widget { /** Current application, application query */ var $application = null; - + /** Owner of this list */ var $owner = null; - + /** Action object using us. */ var $action = null; @@ -87,14 +87,42 @@ class ApplicationList extends Widget function showApplication() { - $this->out->elementStart('li', array('class' => 'application', - 'id' => 'oauthclient-' . $this->application->id)); $user = common_current_user(); - $this->out->raw($this->application->name); - - $this->out->elementEnd('li'); + $this->out->elementStart('li', array('class' => 'application', + 'id' => 'oauthclient-' . $this->application->id)); + + $this->out->elementStart('a', + array('href' => common_local_url( + 'showapplication', + array( + 'nickname' => $user->nickname, + 'id' => $this->application->id + ) + ), + 'class' => 'url') + ); + + $this->out->raw($this->application->name); + $this->out->elementEnd('a'); + + $this->out->raw(' by '); + + $this->out->elementStart('a', + array( + 'href' => $this->application->homepage, + 'class' => 'url' + ) + ); + $this->out->raw($this->application->organization); + $this->out->elementEnd('a'); + + $this->out->elementStart('p', 'note'); + $this->out->raw($this->application->description); + $this->out->elementEnd('p'); + + $this->out->elementEnd('li'); } /* Override this in subclasses. */ diff --git a/lib/default.php b/lib/default.php index e3a043de1..b6ee72279 100644 --- a/lib/default.php +++ b/lib/default.php @@ -211,6 +211,8 @@ $default = 'uploads' => true, 'filecommand' => '/usr/bin/file', ), + 'application' => + array('desclimit' => null), 'group' => array('maxaliases' => 3, 'desclimit' => null), diff --git a/lib/router.php b/lib/router.php index 7b65ae215..a8dbbf6d0 100644 --- a/lib/router.php +++ b/lib/router.php @@ -641,13 +641,30 @@ class Router array('nickname' => '[a-zA-Z0-9]{1,64}')); } - $m->connect('apps/new', array('action' => 'newapplication')); - - $m->connect(':nickname/apps/edit', + $m->connect(':nickname/apps', + array('action' => 'apps'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}')); + $m->connect(':nickname/apps/show/:id', + array('action' => 'showapplication'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}', + 'id' => '[0-9]+') + ); + $m->connect(':nickname/apps/new', + array('action' => 'newapplication'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}')); + $m->connect(':nickname/apps/edit/:id', array('action' => 'editapplication'), - array('nickname' => '['.NICKNAME_FMT.']{1,64}') + array('nickname' => '['.NICKNAME_FMT.']{1,64}', + 'id' => '[0-9]+') ); + $m->connect('oauth/request_token', + array('action' => 'oauthrequesttoken')); + $m->connect('oauth/access_token', + array('action' => 'oauthaccesstoken')); + $m->connect('oauth/authorize', + array('action' => 'oauthauthorize')); + foreach (array('subscriptions', 'subscribers') as $a) { $m->connect(':nickname/'.$a.'/:tag', array('action' => $a), -- cgit v1.2.3-54-g00ecf From 1e5b2a497e3c70e4af5f93e2326c93beed15fed1 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 16 Nov 2009 18:12:39 -0800 Subject: Added session token checking. --- actions/newapplication.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/actions/newapplication.php b/actions/newapplication.php index 9d8635270..ec0f2e7af 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -84,6 +84,13 @@ class NewApplicationAction extends OwnerDesignAction if ($_SERVER['REQUEST_METHOD'] == 'POST') { + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } + $cur = common_current_user(); if ($this->arg('cancel')) { -- cgit v1.2.3-54-g00ecf From 48e5f2b3c5164aa9e47289e5b243e2e1189b71ef Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 7 Jan 2010 01:55:57 -0800 Subject: Add icons/icon upload to Oauth apps --- actions/editapplication.php | 79 +++++++++++++++++++++------------ actions/newapplication.php | 100 ++++++++++++++++++++++++++++++++---------- actions/showapplication.php | 9 +++- classes/Oauth_application.php | 13 ++++++ lib/applicationeditform.php | 43 ++++++++++++++++-- lib/applicationlist.php | 4 ++ 6 files changed, 192 insertions(+), 56 deletions(-) diff --git a/actions/editapplication.php b/actions/editapplication.php index 3af482844..6b8dd501c 100644 --- a/actions/editapplication.php +++ b/actions/editapplication.php @@ -81,7 +81,7 @@ class EditApplicationAction extends OwnerDesignAction /** * Handle the request * - * On GET, show the form. On POST, try to save the group. + * On GET, show the form. On POST, try to save the app. * * @param array $args unused * @@ -91,31 +91,49 @@ class EditApplicationAction extends OwnerDesignAction function handle($args) { parent::handle($args); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $this->handlePost($args); + } else { + $this->showForm(); + } + } - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->clientError(_('There was a problem with your session token.')); - return; - } - - $cur = common_current_user(); - - if ($this->arg('cancel')) { - common_redirect(common_local_url('showapplication', - array( - 'nickname' => $cur->nickname, - 'id' => $this->app->id) - ), 303); - } elseif ($this->arg('save')) { - $this->trySave(); - } else { - $this->clientError(_('Unexpected form submission.')); - } - } else { - $this->showForm(); + function handlePost($args) + { + // Workaround for PHP returning empty $_POST and $_FILES when POST + // length > post_max_size in php.ini + + if (empty($_FILES) + && empty($_POST) + && ($_SERVER['CONTENT_LENGTH'] > 0) + ) { + $msg = _('The server was unable to handle that much POST ' . + 'data (%s bytes) due to its current configuration.'); + $this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH'])); + return; } + + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } + + $cur = common_current_user(); + + if ($this->arg('cancel')) { + common_redirect(common_local_url('showapplication', + array( + 'nickname' => $cur->nickname, + 'id' => $this->app->id) + ), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } } function showForm($msg=null) @@ -149,7 +167,7 @@ class EditApplicationAction extends OwnerDesignAction $homepage = $this->trimmed('homepage'); $callback_url = $this->trimmed('callback_url'); $type = $this->arg('app_type'); - $access_type = $this->arg('access_type'); + $access_type = $this->arg('default_access_type'); if (empty($name)) { $this->showForm(_('Name is required.')); @@ -214,6 +232,7 @@ class EditApplicationAction extends OwnerDesignAction // Checked in prepare() above assert(!is_null($cur)); + assert(!is_null($this->app)); $orig = clone($this->app); @@ -225,16 +244,18 @@ class EditApplicationAction extends OwnerDesignAction $this->app->callback_url = $callback_url; $this->app->type = $type; + $result = $this->app->update($orig); + + common_debug("access_type = $access_type"); + if ($access_type == 'r') { - $this->app->setAccessFlags(true, false); + $this->app->access_type = 1; } else { - $this->app->setAccessFlags(true, true); + $this->app->access_type = 3; } - $result = $this->app->update($orig); - if (!$result) { - common_log_db_error($app, 'UPDATE', __FILE__); + common_log_db_error($this->app, 'UPDATE', __FILE__); $this->serverError(_('Could not update application.')); } diff --git a/actions/newapplication.php b/actions/newapplication.php index ec0f2e7af..a0e61d288 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -71,7 +71,7 @@ class NewApplicationAction extends OwnerDesignAction /** * Handle the request * - * On GET, show the form. On POST, try to save the group. + * On GET, show the form. On POST, try to save the app. * * @param array $args unused * @@ -83,29 +83,46 @@ class NewApplicationAction extends OwnerDesignAction parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { - - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->clientError(_('There was a problem with your session token.')); - return; - } - - $cur = common_current_user(); - - if ($this->arg('cancel')) { - common_redirect(common_local_url('apps', - array('nickname' => $cur->nickname)), 303); - } elseif ($this->arg('save')) { - $this->trySave(); - } else { - $this->clientError(_('Unexpected form submission.')); - } + $this->handlePost($args); } else { $this->showForm(); } } + function handlePost($args) + { + // Workaround for PHP returning empty $_POST and $_FILES when POST + // length > post_max_size in php.ini + + if (empty($_FILES) + && empty($_POST) + && ($_SERVER['CONTENT_LENGTH'] > 0) + ) { + $msg = _('The server was unable to handle that much POST ' . + 'data (%s bytes) due to its current configuration.'); + $this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH'])); + return; + } + + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } + + $cur = common_current_user(); + + if ($this->arg('cancel')) { + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } + } + function showForm($msg=null) { $this->msg = $msg; @@ -130,14 +147,14 @@ class NewApplicationAction extends OwnerDesignAction function trySave() { - $name = $this->trimmed('name'); + $name = $this->trimmed('name'); $description = $this->trimmed('description'); $source_url = $this->trimmed('source_url'); $organization = $this->trimmed('organization'); $homepage = $this->trimmed('homepage'); $callback_url = $this->trimmed('callback_url'); $type = $this->arg('app_type'); - $access_type = $this->arg('access_type'); + $access_type = $this->arg('default_access_type'); if (empty($name)) { $this->showForm(_('Name is required.')); @@ -241,14 +258,16 @@ class NewApplicationAction extends OwnerDesignAction $app->consumer_key = $consumer->consumer_key; - $result = $app->insert(); + $this->app_id = $app->insert(); - if (!$result) { + if (!$this->app_id) { common_log_db_error($app, 'INSERT', __FILE__); $this->serverError(_('Could not create application.')); $app->query('ROLLBACK'); } + $this->uploadLogo($app); + $app->query('COMMIT'); common_redirect(common_local_url('apps', @@ -256,5 +275,40 @@ class NewApplicationAction extends OwnerDesignAction } + /** + * Handle an image upload + * + * Does all the magic for handling an image upload, and crops the + * image by default. + * + * @return void + */ + + function uploadLogo($app) + { + if ($_FILES['app_icon']['error'] == + UPLOAD_ERR_OK) { + + try { + $imagefile = ImageFile::fromUpload('app_icon'); + } catch (Exception $e) { + common_debug("damn that sucks"); + $this->showForm($e->getMessage()); + return; + } + + $filename = Avatar::filename($app->id, + image_type_to_extension($imagefile->type), + null, + 'oauth-app-icon-'.common_timestamp()); + + $filepath = Avatar::path($filename); + + move_uploaded_file($imagefile->filepath, $filepath); + + $app->setOriginal($filename); + } + } + } diff --git a/actions/showapplication.php b/actions/showapplication.php index 6b8eff4a6..6d19b9561 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -55,7 +55,6 @@ class ShowApplicationAction extends OwnerDesignAction var $owner = null; - var $msg = null; var $success = null; @@ -187,6 +186,14 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementStart('ul', 'entity_application_details'); + $this->elementStart('li', 'entity_application-icon'); + + if (!empty($this->application->icon)) { + $this->element('img', array('src' => $this->application->icon)); + } + + $this->elementEnd('li'); + $this->elementStart('li', 'entity_application_name'); $this->element('span', array('class' => 'big'), $this->application->name); $this->raw(sprintf(_(' by %1$s'), $this->application->organization)); diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php index ef1bbf6d9..d4de6d82e 100644 --- a/classes/Oauth_application.php +++ b/classes/Oauth_application.php @@ -75,4 +75,17 @@ class Oauth_application extends Memcached_DataObject } } + function setOriginal($filename) + { + $imagefile = new ImageFile($this->id, Avatar::path($filename)); + + // XXX: Do we want to have a bunch of different size icons? homepage, stream, mini? + // or just one and control size via CSS? --Zach + + $orig = clone($this); + $this->icon = Avatar::url($filename); + common_debug(common_log_objstring($this)); + return $this->update($orig); + } + } diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index ed187ba0b..4d3bb06e7 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -81,6 +81,21 @@ class ApplicationEditForm extends Form } } + /** + * HTTP method used to submit the form + * + * For image data we need to send multipart/form-data + * so we set that here too + * + * @return string the method to use for submitting + */ + + function method() + { + $this->enctype = 'multipart/form-data'; + return 'post'; + } + /** * class of the form * @@ -134,6 +149,7 @@ class ApplicationEditForm extends Form { if ($this->application) { $id = $this->application->id; + $icon = $this->application->icon; $name = $this->application->name; $description = $this->application->description; $source_url = $this->application->source_url; @@ -144,6 +160,7 @@ class ApplicationEditForm extends Form $this->access_type = $this->application->access_type; } else { $id = ''; + $icon = ''; $name = ''; $description = ''; $source_url = ''; @@ -154,11 +171,31 @@ class ApplicationEditForm extends Form $this->access_type = ''; } + $this->out->hidden('token', common_session_token()); + $this->out->elementStart('ul', 'form_data'); - $this->out->elementStart('li'); + + $this->out->elementStart('li'); + + if (!empty($icon)) { + $this->out->element('img', array('src' => $icon)); + } + + $this->out->element('label', array('for' => 'app_icon'), + _('Icon')); + $this->out->element('input', array('name' => 'app_icon', + 'type' => 'file', + 'id' => 'app_icon')); + $this->out->element('p', 'form_guide', _('Icon for this application')); + $this->out->element('input', array('name' => 'MAX_FILE_SIZE', + 'type' => 'hidden', + 'id' => 'MAX_FILE_SIZE', + 'value' => ImageFile::maxFileSizeInt())); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); $this->out->hidden('application_id', $id); - $this->out->hidden('token', common_session_token()); $this->out->input('name', _('Name'), ($this->out->arg('name')) ? $this->out->arg('name') : $name); @@ -215,7 +252,7 @@ class ApplicationEditForm extends Form // Default to Browser if ($this->application->type == Oauth_application::$browser - || empty($this->applicaiton->type)) { + || empty($this->application->type)) { $attrs['checked'] = 'checked'; } diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 3141ea974..5392ddab8 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -93,6 +93,10 @@ class ApplicationList extends Widget $this->out->elementStart('li', array('class' => 'application', 'id' => 'oauthclient-' . $this->application->id)); + if (!empty($this->application->icon)) { + $this->out->element('img', array('src' => $this->application->icon)); + } + $this->out->elementStart('a', array('href' => common_local_url( 'showapplication', -- cgit v1.2.3-54-g00ecf From 6472331be51bc6d0e670603b2a89fb66022f6b51 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 7 Jan 2010 13:19:21 -0800 Subject: Stubs for API OAuth token exchange stuff --- actions/apioauthaccesstoken.php | 49 ++++++++++++++++++++++++++++++++++++++++ actions/apioauthauthorize.php | 49 ++++++++++++++++++++++++++++++++++++++++ actions/apioauthrequesttoken.php | 49 ++++++++++++++++++++++++++++++++++++++++ lib/router.php | 6 ++--- 4 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 actions/apioauthaccesstoken.php create mode 100644 actions/apioauthauthorize.php create mode 100644 actions/apioauthrequesttoken.php diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php new file mode 100644 index 000000000..db82f656a --- /dev/null +++ b/actions/apioauthaccesstoken.php @@ -0,0 +1,49 @@ +. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Exchange an authorized OAuth request token for an access token + * + * @category API + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiOauthAccessTokenAction extends ApiAction +{ + +} diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php new file mode 100644 index 000000000..8839d9571 --- /dev/null +++ b/actions/apioauthauthorize.php @@ -0,0 +1,49 @@ +. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Authorize an OAuth request token + * + * @category API + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiOauthAuthorizeAction extends Action +{ + +} diff --git a/actions/apioauthrequesttoken.php b/actions/apioauthrequesttoken.php new file mode 100644 index 000000000..c1ccd4b7d --- /dev/null +++ b/actions/apioauthrequesttoken.php @@ -0,0 +1,49 @@ +. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Get an OAuth request token + * + * @category API + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiOauthRequestTokenAction extends ApiAction +{ + +} diff --git a/lib/router.php b/lib/router.php index a8dbbf6d0..0703d7597 100644 --- a/lib/router.php +++ b/lib/router.php @@ -659,11 +659,11 @@ class Router ); $m->connect('oauth/request_token', - array('action' => 'oauthrequesttoken')); + array('action' => 'apioauthrequesttoken')); $m->connect('oauth/access_token', - array('action' => 'oauthaccesstoken')); + array('action' => 'apioauthaccesstoken')); $m->connect('oauth/authorize', - array('action' => 'oauthauthorize')); + array('action' => 'apioauthauthorize')); foreach (array('subscriptions', 'subscribers') as $a) { $m->connect(':nickname/'.$a.'/:tag', -- cgit v1.2.3-54-g00ecf From fa81a580bb9eea76e7739f37010b35e4b919f410 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 7 Jan 2010 18:33:17 -0800 Subject: Action for issuing a request token --- actions/apioauthrequesttoken.php | 41 +++++++++++++++++- lib/apioauthstore.php | 90 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 lib/apioauthstore.php diff --git a/actions/apioauthrequesttoken.php b/actions/apioauthrequesttoken.php index c1ccd4b7d..1bbd7d295 100644 --- a/actions/apioauthrequesttoken.php +++ b/actions/apioauthrequesttoken.php @@ -32,6 +32,7 @@ if (!defined('STATUSNET')) { } require_once INSTALLDIR . '/lib/api.php'; +require_once INSTALLDIR . '/lib/apioauthstore.php'; /** * Get an OAuth request token @@ -43,7 +44,45 @@ require_once INSTALLDIR . '/lib/api.php'; * @link http://status.net/ */ -class ApiOauthRequestTokenAction extends ApiAction +class ApiOauthRequestTokenAction extends Action { + /** + * Is read only? + * + * @return boolean false + */ + function isReadOnly() + { + return false; + } + + /** + * Class handler. + * + * @param array $args array of arguments + * + * @return void + */ + function handle($args) + { + parent::handle($args); + + $datastore = new ApiStatusNetOAuthDataStore(); + $server = new OAuthServer($datastore); + $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + $server->add_signature_method($hmac_method); + + try { + $req = OAuthRequest::from_request(); + $token = $server->fetch_request_token($req); + print $token; + } catch (OAuthException $e) { + common_log(LOG_WARN, $e->getMessage()); + common_debug(var_export($req, true)); + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/html; charset=utf-8'); + print $e->getMessage() . "\n"; + } + } } diff --git a/lib/apioauthstore.php b/lib/apioauthstore.php new file mode 100644 index 000000000..a92a4d6e4 --- /dev/null +++ b/lib/apioauthstore.php @@ -0,0 +1,90 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +require_once INSTALLDIR . '/lib/oauthstore.php'; + +class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore +{ + + function lookup_consumer($consumer_key) + { + $con = Consumer::staticGet('consumer_key', $consumer_key); + + if (!$con) { + return null; + } + + return new OAuthConsumer($con->consumer_key, + $con->consumer_secret); + } + + function new_access_token($token, $consumer) + { + common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__); + $rt = new Token(); + $rt->consumer_key = $consumer->key; + $rt->tok = $token->key; + $rt->type = 0; // request + if ($rt->find(true) && $rt->state == 1) { // authorized + common_debug('request token found.', __FILE__); + $at = new Token(); + $at->consumer_key = $consumer->key; + $at->tok = common_good_rand(16); + $at->secret = common_good_rand(16); + $at->type = 1; // access + $at->created = DB_DataObject_Cast::dateTime(); + if (!$at->insert()) { + $e = $at->_lastError; + common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__); + return null; + } else { + common_debug('access token "'.$at->tok.'" inserted', __FILE__); + // burn the old one + $orig_rt = clone($rt); + $rt->state = 2; // used + if (!$rt->update($orig_rt)) { + return null; + } + common_debug('request token "'.$rt->tok.'" updated', __FILE__); + // Update subscription + // XXX: mixing levels here + $sub = Subscription::staticGet('token', $rt->tok); + if (!$sub) { + return null; + } + common_debug('subscription for request token found', __FILE__); + $orig_sub = clone($sub); + $sub->token = $at->tok; + $sub->secret = $at->secret; + if (!$sub->update($orig_sub)) { + return null; + } else { + common_debug('subscription updated to use access token', __FILE__); + return new OAuthToken($at->tok, $at->secret); + } + } + } else { + return null; + } + } + +} + -- cgit v1.2.3-54-g00ecf From e9e448bcee69b0c39badf353faedb4c29af3f502 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 10 Jan 2010 21:35:46 -0800 Subject: Workflow for request tokens and authorizing request tokens --- actions/apioauthauthorize.php | 326 ++++++++++++++++++++++++++++++++++++++- actions/apioauthrequesttoken.php | 5 +- actions/showapplication.php | 6 +- lib/router.php | 19 +-- 4 files changed, 338 insertions(+), 18 deletions(-) diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index 8839d9571..895a0c6e5 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/api.php'; +require_once INSTALLDIR . '/lib/apioauthstore.php'; /** * Authorize an OAuth request token @@ -45,5 +45,329 @@ require_once INSTALLDIR . '/lib/api.php'; class ApiOauthAuthorizeAction extends Action { + var $oauth_token; + var $callback; + var $app; + var $nickname; + var $password; + var $store; + + /** + * Is this a read-only action? + * + * @return boolean false + */ + + function isReadOnly($args) + { + return false; + } + + function prepare($args) + { + parent::prepare($args); + + common_debug(var_export($_REQUEST, true)); + + $this->nickname = $this->trimmed('nickname'); + $this->password = $this->arg('password'); + $this->oauth_token = $this->arg('oauth_token'); + $this->callback = $this->arg('oauth_callback'); + $this->store = new ApiStatusNetOAuthDataStore(); + + return true; + } + + function getApp() + { + // Look up the full req token + + $req_token = $this->store->lookup_token(null, + 'request', + $this->oauth_token); + + if (empty($req_token)) { + + common_debug("Couldn't find request token!"); + + $this->clientError(_('Bad request.')); + return; + } + + // Look up the app + + $app = new Oauth_application(); + $app->consumer_key = $req_token->consumer_key; + $result = $app->find(true); + + if (!empty($result)) { + $this->app = $app; + return true; + + } else { + common_debug("couldn't find the app!"); + return false; + } + } + + /** + * Handle input, produce output + * + * Switches on request method; either shows the form or handles its input. + * + * @param array $args $_REQUEST data + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + /* Use a session token for CSRF protection. */ + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + $this->handlePost(); + + } else { + + common_debug('ApiOauthAuthorize::handle()'); + + if (empty($this->oauth_token)) { + + common_debug("No request token found."); + + $this->clientError(_('Bad request.')); + return; + } + + if (!$this->getApp()) { + $this->clientError(_('Bad request.')); + return; + } + + common_debug("Requesting auth for app: $app->name."); + + $this->showForm(); + } + } + + function handlePost() + { + /* Use a session token for CSRF protection. */ + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + if (!$this->getApp()) { + $this->clientError(_('Bad request.')); + return; + } + + // is the user already logged in? + + // check creds + + if (!common_logged_in()) { + $user = common_check_user($this->nickname, $this->password); + if (empty($user)) { + $this->showForm(_("Invalid nickname / password!")); + return; + } + } + + if ($this->arg('allow')) { + + $this->store->authorize_token($this->oauth_token); + + // if we have a callback redirect and provide the token + + if (!empty($this->callback)) { + $target_url = $this->callback . '?oauth_token=' . $this->oauth_token; + common_redirect($target_url, 303); + } + + // otherwise inform the user that the rt was authorized + + $this->elementStart('p'); + + // XXX: Do verifier code? + + $this->raw(sprintf(_("The request token %s has been authorized. " . + 'Please exchange it for an access token.'), + $this->oauth_token)); + + $this->elementEnd('p'); + + } else if ($this->arg('deny')) { + + $this->elementStart('p'); + + $this->raw(sprintf(_("The request token %s has been denied."), + $this->oauth_token)); + + $this->elementEnd('p'); + } else { + $this->clientError(_('Unexpected form submission.')); + return; + } + } + + function showForm($error=null) + { + $this->error = $error; + $this->showPage(); + } + + function showScripts() + { + parent::showScripts(); + // $this->autofocus('nickname'); + } + + /** + * Title of the page + * + * @return string title of the page + */ + + function title() + { + return _('An application would like to connect to your account'); + } + + /** + * Show page notice + * + * Display a notice for how to use the page, or the + * error if it exists. + * + * @return void + */ + + function showPageNotice() + { + if ($this->error) { + $this->element('p', 'error', $this->error); + } else { + $instr = $this->getInstructions(); + $output = common_markup_to_html($instr); + + $this->raw($output); + } + } + + /** + * Shows the authorization form. + * + * @return void + */ + + function showContent() + { + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_login', + 'class' => 'form_settings', + 'action' => common_local_url('apioauthauthorize'))); + + $this->hidden('token', common_session_token()); + $this->hidden('oauth_token', $this->oauth_token); + $this->hidden('oauth_callback', $this->callback); + + $this->elementStart('fieldset'); + + $this->elementStart('ul'); + $this->elementStart('li'); + if (!empty($this->app->icon)) { + $this->element('img', array('src' => $this->app->icon)); + } + $this->elementEnd('li'); + $this->elementStart('li'); + + $access = ($this->app->access_type & Oauth_application::$writeAccess) ? + 'access and update' : 'access'; + + $msg = _("The application %s by %s would like " . + "the ability to %s your account data."); + + $this->raw(sprintf($msg, + $this->app->name, + $this->app->organization, + $access)); + + $this->elementEnd('li'); + $this->elementEnd('ul'); + + $this->elementEnd('fieldset'); + + if (!common_logged_in()) { + + $this->elementStart('fieldset'); + $this->element('legend', null, _('Login')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('nickname', _('Nickname')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->password('password', _('Password')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + + $this->elementEnd('fieldset'); + + } + + $this->element('input', array('id' => 'deny_submit', + 'class' => 'submit', + 'name' => 'deny', + 'type' => 'submit', + 'value' => _('Deny'))); + + $this->element('input', array('id' => 'allow_submit', + 'class' => 'submit', + 'name' => 'allow', + 'type' => 'submit', + 'value' => _('Allow'))); + + $this->elementEnd('form'); + } + + /** + * Instructions for using the form + * + * For "remembered" logins, we make the user re-login when they + * try to change settings. Different instructions for this case. + * + * @return void + */ + + function getInstructions() + { + return _('Allow or deny access to your account information.'); + + } + + /** + * A local menu + * + * Shows different login/register actions. + * + * @return void + */ + + function showLocalNav() + { + } } diff --git a/actions/apioauthrequesttoken.php b/actions/apioauthrequesttoken.php index 1bbd7d295..53aca6b96 100644 --- a/actions/apioauthrequesttoken.php +++ b/actions/apioauthrequesttoken.php @@ -31,7 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/api.php'; require_once INSTALLDIR . '/lib/apioauthstore.php'; /** @@ -70,6 +69,7 @@ class ApiOauthRequestTokenAction extends Action $datastore = new ApiStatusNetOAuthDataStore(); $server = new OAuthServer($datastore); $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + $server->add_signature_method($hmac_method); try { @@ -77,8 +77,7 @@ class ApiOauthRequestTokenAction extends Action $token = $server->fetch_request_token($req); print $token; } catch (OAuthException $e) { - common_log(LOG_WARN, $e->getMessage()); - common_debug(var_export($req, true)); + common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); header('HTTP/1.1 401 Unauthorized'); header('Content-Type: text/html; charset=utf-8'); print $e->getMessage() . "\n"; diff --git a/actions/showapplication.php b/actions/showapplication.php index 6d19b9561..5156fa6f0 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -231,17 +231,17 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementStart('dl', 'entity_request_token_url'); $this->element('dt', null, _('Request token URL')); - $this->element('dd', 'label', common_local_url('oauthrequesttoken')); + $this->element('dd', 'label', common_local_url('apioauthrequesttoken')); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_access_token_url'); $this->element('dt', null, _('Access token URL')); - $this->element('dd', 'label', common_local_url('oauthaccesstoken')); + $this->element('dd', 'label', common_local_url('apioauthaccesstoken')); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_authorize_url'); $this->element('dt', null, _('Authorize URL')); - $this->element('dd', 'label', common_local_url('oauthauthorize')); + $this->element('dd', 'label', common_local_url('apioauthauthorize')); $this->elementEnd('dl'); $this->element('p', 'oauth-signature-note', diff --git a/lib/router.php b/lib/router.php index 0703d7597..420f5a0a1 100644 --- a/lib/router.php +++ b/lib/router.php @@ -50,7 +50,8 @@ class Router var $m = null; static $inst = null; static $bare = array('requesttoken', 'accesstoken', 'userauthorization', - 'postnotice', 'updateprofile', 'finishremotesubscribe'); + 'postnotice', 'updateprofile', 'finishremotesubscribe', + 'apioauthrequesttoken', 'apioauthaccesstoken'); static function get() { @@ -144,7 +145,7 @@ class Router 'email', 'sms', 'userdesign', 'other') as $s) { $m->connect('settings/'.$s, array('action' => $s.'settings')); } - + // search foreach (array('group', 'people', 'notice') as $s) { @@ -640,11 +641,11 @@ class Router array('action' => $a), array('nickname' => '[a-zA-Z0-9]{1,64}')); } - - $m->connect(':nickname/apps', + + $m->connect(':nickname/apps', array('action' => 'apps'), array('nickname' => '['.NICKNAME_FMT.']{1,64}')); - $m->connect(':nickname/apps/show/:id', + $m->connect(':nickname/apps/show/:id', array('action' => 'showapplication'), array('nickname' => '['.NICKNAME_FMT.']{1,64}', 'id' => '[0-9]+') @@ -652,18 +653,14 @@ class Router $m->connect(':nickname/apps/new', array('action' => 'newapplication'), array('nickname' => '['.NICKNAME_FMT.']{1,64}')); - $m->connect(':nickname/apps/edit/:id', + $m->connect(':nickname/apps/edit/:id', array('action' => 'editapplication'), array('nickname' => '['.NICKNAME_FMT.']{1,64}', 'id' => '[0-9]+') ); - $m->connect('oauth/request_token', - array('action' => 'apioauthrequesttoken')); - $m->connect('oauth/access_token', - array('action' => 'apioauthaccesstoken')); $m->connect('oauth/authorize', - array('action' => 'apioauthauthorize')); + array('action' => 'apioauthauthorize')); foreach (array('subscriptions', 'subscribers') as $a) { $m->connect(':nickname/'.$a.'/:tag', -- cgit v1.2.3-54-g00ecf From c473a39a7da07fbe5b80fec4c08111a554691c3a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 10 Jan 2010 23:03:30 -0800 Subject: Associate request tokens with OAuth apps and app users --- actions/apioauthauthorize.php | 64 +++++++++++++++++++++++++++++--------- classes/Oauth_application_user.php | 24 +++++++++++++- classes/statusnet.ini | 4 +++ db/statusnet.sql | 5 ++- 4 files changed, 81 insertions(+), 16 deletions(-) diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index 895a0c6e5..48d5087ef 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -125,19 +125,12 @@ class ApiOauthAuthorizeAction extends Action parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { - /* Use a session token for CSRF protection. */ - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } $this->handlePost(); } else { - common_debug('ApiOauthAuthorize::handle()'); + // XXX: make better error messages if (empty($this->oauth_token)) { @@ -160,7 +153,7 @@ class ApiOauthAuthorizeAction extends Action function handlePost() { - /* Use a session token for CSRF protection. */ + // check session token for CSRF protection. $token = $this->trimmed('token'); @@ -175,25 +168,66 @@ class ApiOauthAuthorizeAction extends Action return; } - // is the user already logged in? - // check creds + $user = null; + if (!common_logged_in()) { $user = common_check_user($this->nickname, $this->password); if (empty($user)) { $this->showForm(_("Invalid nickname / password!")); return; } - } + } else { + $user = common_current_user(); + } if ($this->arg('allow')) { + // mark the req token as authorized + $this->store->authorize_token($this->oauth_token); + // Check to see if there was a previous token associated + // with this user/app and kill it. If you're doing this you + // probably don't want any old tokens anyway. + + $appUser = Oauth_application_user::getByKeys($user, $this->app); + + if (!empty($appUser)) { + $result = $appUser->delete(); + + if (!$result) { + common_log_db_error($appUser, 'DELETE', __FILE__); + throw new ServerException(_('DB error deleting OAuth app user.')); + return; + } + } + + // associated the new req token with the user and the app + + $appUser = new Oauth_application_user(); + + $appUser->profile_id = $user->id; + $appUser->application_id = $this->app->id; + $appUser->access_type = $this->app->access_type; + $appUser->token = $this->oauth_token; + $appUser->created = common_sql_now(); + + $result = $appUser->insert(); + + if (!$result) { + common_log_db_error($appUser, 'INSERT', __FILE__); + throw new ServerException(_('DB error inserting OAuth app user.')); + return; + } + // if we have a callback redirect and provide the token if (!empty($this->callback)) { + + // XXX: Need better way to build this redirect url. + $target_url = $this->callback . '?oauth_token=' . $this->oauth_token; common_redirect($target_url, 303); } @@ -202,7 +236,7 @@ class ApiOauthAuthorizeAction extends Action $this->elementStart('p'); - // XXX: Do verifier code? + // XXX: Do OAuth 1.0a verifier code? $this->raw(sprintf(_("The request token %s has been authorized. " . 'Please exchange it for an access token.'), @@ -233,7 +267,9 @@ class ApiOauthAuthorizeAction extends Action function showScripts() { parent::showScripts(); - // $this->autofocus('nickname'); + if (!common_logged_in()) { + $this->autofocus('nickname'); + } } /** diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php index 9e45ece25..e4c018f21 100644 --- a/classes/Oauth_application_user.php +++ b/classes/Oauth_application_user.php @@ -13,12 +13,34 @@ class Oauth_application_user extends Memcached_DataObject public $profile_id; // int(4) primary_key not_null public $application_id; // int(4) primary_key not_null public $access_type; // tinyint(1) + public $token; // varchar(255) + public $secret; // varchar(255) + public $verifier; // varchar(255) public $created; // datetime not_null + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP /* Static get */ function staticGet($k,$v=NULL) { - return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v); + return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + + static function getByKeys($user, $app) + { + if (empty($user) || empty($app)) { + return null; + } + + $oau = new Oauth_application_user(); + + $oau->profile_id = $user->id; + $oau->application_id = $app->id; + $oau->limit(1); + + $result = $oau->find(true); + + return empty($result) ? null : $oau; + } + } diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 0cbe60a5a..43f6c4466 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -372,7 +372,11 @@ id = N profile_id = 129 application_id = 129 access_type = 17 +token = 2 +secret = 2 +verifier = 2 created = 142 +modified = 384 [oauth_application_user__keys] profile_id = K diff --git a/db/statusnet.sql b/db/statusnet.sql index a2740b60c..eb4706067 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -229,8 +229,11 @@ create table oauth_application_user ( profile_id integer not null comment 'user of the application' references profile (id), application_id integer not null comment 'id of the application' references oauth_application (id), access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked', + token varchar(255) comment 'authorization token', + secret varchar(255) comment 'token secret', + verifier varchar(255) not null comment 'verification code', created datetime not null comment 'date this record was created', - + modified timestamp comment 'date this record was modified', constraint primary key (profile_id, application_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; -- cgit v1.2.3-54-g00ecf From a0b84387737b016168eb3b9a1c6dee1980724f66 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 11 Jan 2010 01:11:50 -0800 Subject: Exchanging authorized request tokens for access tokens working --- actions/apioauthaccesstoken.php | 60 ++++++++++++++++++++++++++++++++++-- classes/Oauth_application.php | 14 +++++++++ lib/apioauthstore.php | 68 ++++++++++++++++++++++++++++------------- 3 files changed, 119 insertions(+), 23 deletions(-) diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php index db82f656a..9b99724d0 100644 --- a/actions/apioauthaccesstoken.php +++ b/actions/apioauthaccesstoken.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/api.php'; +require_once INSTALLDIR . '/lib/apioauthstore.php'; /** * Exchange an authorized OAuth request token for an access token @@ -43,7 +43,63 @@ require_once INSTALLDIR . '/lib/api.php'; * @link http://status.net/ */ -class ApiOauthAccessTokenAction extends ApiAction +class ApiOauthAccessTokenAction extends Action { + /** + * Is read only? + * + * @return boolean false + */ + function isReadOnly() + { + return false; + } + + /** + * Class handler. + * + * @param array $args array of arguments + * + * @return void + */ + function handle($args) + { + parent::handle($args); + + $datastore = new ApiStatusNetOAuthDataStore(); + $server = new OAuthServer($datastore); + $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + + $server->add_signature_method($hmac_method); + + $atok = null; + + try { + $req = OAuthRequest::from_request(); + $atok = $server->fetch_access_token($req); + + } catch (OAuthException $e) { + common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); + common_debug(var_export($req, true)); + $this->outputError($e->getMessage()); + return; + } + + if (empty($atok)) { + common_debug('couldn\'t get access token.'); + $this->outputError("Badness."); + return; + } + + print $atok; + } + + function outputError($msg) + { + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/html; charset=utf-8'); + print $msg . "\n"; + } } + diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php index d4de6d82e..5df8b9459 100644 --- a/classes/Oauth_application.php +++ b/classes/Oauth_application.php @@ -88,4 +88,18 @@ class Oauth_application extends Memcached_DataObject return $this->update($orig); } + static function getByConsumerKey($key) + { + if (empty($key)) { + return null; + } + + $app = new Oauth_application(); + $app->consumer_key = $key; + $app->limit(1); + $result = $app->find(true); + + return empty($result) ? null : $app; + } + } diff --git a/lib/apioauthstore.php b/lib/apioauthstore.php index a92a4d6e4..290ce8973 100644 --- a/lib/apioauthstore.php +++ b/lib/apioauthstore.php @@ -39,19 +39,45 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore function new_access_token($token, $consumer) { common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__); - $rt = new Token(); + + $rt = new Token(); $rt->consumer_key = $consumer->key; $rt->tok = $token->key; $rt->type = 0; // request - if ($rt->find(true) && $rt->state == 1) { // authorized + + $app = Oauth_application::getByConsumerKey($consumer->key); + + if (empty($app)) { + common_debug("empty app!"); + } + + if ($rt->find(true) && $rt->state == 1) { // authorized common_debug('request token found.', __FILE__); - $at = new Token(); + + // find the associated user of the app + + $appUser = new Oauth_application_user(); + $appUser->application_id = $app->id; + $appUser->token = $rt->tok; + $result = $appUser->find(true); + + if (!empty($result)) { + common_debug("Oath app user found."); + } else { + common_debug("Oauth app user not found."); + return null; + } + + // go ahead and make the access token + + $at = new Token(); $at->consumer_key = $consumer->key; $at->tok = common_good_rand(16); $at->secret = common_good_rand(16); $at->type = 1; // access $at->created = DB_DataObject_Cast::dateTime(); - if (!$at->insert()) { + + if (!$at->insert()) { $e = $at->_lastError; common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__); return null; @@ -64,23 +90,23 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore return null; } common_debug('request token "'.$rt->tok.'" updated', __FILE__); - // Update subscription - // XXX: mixing levels here - $sub = Subscription::staticGet('token', $rt->tok); - if (!$sub) { - return null; - } - common_debug('subscription for request token found', __FILE__); - $orig_sub = clone($sub); - $sub->token = $at->tok; - $sub->secret = $at->secret; - if (!$sub->update($orig_sub)) { - return null; - } else { - common_debug('subscription updated to use access token', __FILE__); - return new OAuthToken($at->tok, $at->secret); - } - } + + // update the token from req to access for the user + + $orig = clone($appUser); + $appUser->token = $at->tok; + $result = $appUser->update($orig); + + if (empty($result)) { + common_debug('couldn\'t update OAuth app user.'); + return null; + } + + // Okay, good + + return new OAuthToken($at->tok, $at->secret); + } + } else { return null; } -- cgit v1.2.3-54-g00ecf From c2337ab47c183ab9758f5db8632ac9cd480a282a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 11 Jan 2010 12:17:36 -0800 Subject: Decided we didn't need to keep the token secret in the Oauth_application_user record --- classes/Oauth_application_user.php | 1 - classes/statusnet.ini | 1 - db/statusnet.sql | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php index e4c018f21..a05371f56 100644 --- a/classes/Oauth_application_user.php +++ b/classes/Oauth_application_user.php @@ -14,7 +14,6 @@ class Oauth_application_user extends Memcached_DataObject public $application_id; // int(4) primary_key not_null public $access_type; // tinyint(1) public $token; // varchar(255) - public $secret; // varchar(255) public $verifier; // varchar(255) public $created; // datetime not_null public $modified; // timestamp not_null default_CURRENT_TIMESTAMP diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 43f6c4466..b358bbf60 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -373,7 +373,6 @@ profile_id = 129 application_id = 129 access_type = 17 token = 2 -secret = 2 verifier = 2 created = 142 modified = 384 diff --git a/db/statusnet.sql b/db/statusnet.sql index eb4706067..c3160bcf8 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -229,8 +229,7 @@ create table oauth_application_user ( profile_id integer not null comment 'user of the application' references profile (id), application_id integer not null comment 'id of the application' references oauth_application (id), access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked', - token varchar(255) comment 'authorization token', - secret varchar(255) comment 'token secret', + token varchar(255) comment 'request or access token', verifier varchar(255) not null comment 'verification code', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified', -- cgit v1.2.3-54-g00ecf From 11bd98025c1e41921359b634461772d22a1c059f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 11 Jan 2010 12:52:56 -0800 Subject: Issue a warning when someone tries to exchange an unauthorized or otherwise bad req token for an access token. --- actions/apioauthaccesstoken.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php index 9b99724d0..67359d765 100644 --- a/actions/apioauthaccesstoken.php +++ b/actions/apioauthaccesstoken.php @@ -88,11 +88,10 @@ class ApiOauthAccessTokenAction extends Action if (empty($atok)) { common_debug('couldn\'t get access token.'); - $this->outputError("Badness."); - return; + print "Token exchange failed. Has the request token been authorized?\n"; + } else { + print $atok; } - - print $atok; } function outputError($msg) -- cgit v1.2.3-54-g00ecf From c78937537ed17eabb665ec6e4344b564799cbccc Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 11 Jan 2010 14:11:43 -0800 Subject: Better detial in connected OAuth applications list --- actions/oauthconnectionssettings.php | 32 +++++++++++++---- classes/Profile.php | 9 ++--- lib/applicationlist.php | 68 ++++++++++++++++++++++++++---------- 3 files changed, 79 insertions(+), 30 deletions(-) diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index e4b5af158..56e7b02fb 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -48,6 +48,16 @@ require_once INSTALLDIR . '/lib/applicationlist.php'; class OauthconnectionssettingsAction extends ConnectSettingsAction { + + var $page = null; + + function prepare($args) + { + parent::prepare($args); + $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1; + return true; + } + /** * Title of the page * @@ -59,6 +69,11 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction return _('Connected Applications'); } + function isReadOnly($args) + { + return true; + } + /** * Instructions for use * @@ -86,13 +101,16 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction $application = $profile->getApplications($offset, $limit); - if ($application) { - $al = new ApplicationList($application, $this->user, $this); - $cnt = $al->show(); - if (0 == $cnt) { - $this->showEmptyListMessage(); - } - } + $cnt == 0; + + if (!empty($application)) { + $al = new ApplicationList($application, $user, $this, true); + $cnt = $al->show(); + } + + if ($cnt == 0) { + $this->showEmptyListMessage(); + } $this->pagination($this->page > 1, $cnt > APPS_PER_PAGE, $this->page, 'connectionssettings', diff --git a/classes/Profile.php b/classes/Profile.php index 687215b11..fef2a2171 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -355,10 +355,11 @@ class Profile extends Memcached_DataObject function getApplications($offset = 0, $limit = null) { $qry = - 'SELECT oauth_application_user.* ' . - 'FROM oauth_application_user ' . - 'WHERE profile_id = %d ' . - 'ORDER BY created DESC '; + 'SELECT a.* ' . + 'FROM oauth_application_user u, oauth_application a ' . + 'WHERE u.profile_id = %d ' . + 'AND a.id = u.application_id ' . + 'ORDER BY u.created DESC '; if ($offset > 0) { if (common_config('db','type') == 'pgsql') { diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 5392ddab8..e305437f4 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -57,13 +57,14 @@ class ApplicationList extends Widget /** Action object using us. */ var $action = null; - function __construct($application, $owner=null, $action=null) + function __construct($application, $owner=null, $action=null, $connections = false) { parent::__construct($action); $this->application = $application; $this->owner = $owner; $this->action = $action; + $this->connections = $connections; } function show() @@ -97,36 +98,65 @@ class ApplicationList extends Widget $this->out->element('img', array('src' => $this->application->icon)); } - $this->out->elementStart('a', - array('href' => common_local_url( - 'showapplication', - array( - 'nickname' => $user->nickname, - 'id' => $this->application->id - ) - ), - 'class' => 'url') - ); + if (!$this->connections) { + + $this->out->elementStart('a', + array('href' => + common_local_url('showapplication', + array('nickname' => $user->nickname, + 'id' => $this->application->id)), + 'class' => 'url') + ); $this->out->raw($this->application->name); $this->out->elementEnd('a'); + } else { + $this->out->elementStart('a', + array('href' => $this->application->source_url, + 'class' => 'url')); - $this->out->raw(' by '); + $this->out->raw($this->application->name); + $this->out->elementEnd('a'); + } - $this->out->elementStart('a', + $this->out->raw(' by '); + + $this->out->elementStart('a', array( - 'href' => $this->application->homepage, - 'class' => 'url' + 'href' => $this->application->homepage, + 'class' => 'url' ) - ); - $this->out->raw($this->application->organization); - $this->out->elementEnd('a'); + ); + $this->out->raw($this->application->organization); + $this->out->elementEnd('a'); - $this->out->elementStart('p', 'note'); + $this->out->elementStart('p', 'note'); $this->out->raw($this->application->description); $this->out->elementEnd('p'); + $this->out->elementEnd('li'); + + if ($this->connections) { + + $appUser = Oauth_application_user::getByKeys($this->owner, $this->application); + + if (empty($appUser)) { + common_debug("empty appUser!"); + } + + $this->out->elementStart('li'); + + $access = ($this->application->access_type & Oauth_application::$writeAccess) + ? 'read-write' : 'read-only'; + + $txt = 'Approved ' . common_exact_date($appUser->modified) . + " $access for access."; + + $this->out->raw($txt); $this->out->elementEnd('li'); + + // XXX: Add revoke access button + } } /* Override this in subclasses. */ -- cgit v1.2.3-54-g00ecf From 4fb9b43aa2c174e0b5ac3e260cafd2b5fcfa9368 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 11 Jan 2010 22:46:35 +0000 Subject: Updated markup for application edit form submits --- lib/applicationeditform.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index 4d3bb06e7..f8fcb3e3f 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -333,7 +333,9 @@ class ApplicationEditForm extends Form function formActions() { - $this->out->submit('save', _('Save')); - $this->out->submit('cancel', _('Cancel')); + $this->out->submit('cancel', _('Cancel'), 'submit form_action-primary', + 'cancel', _('Cancel')); + $this->out->submit('save', _('Save'), 'submit form_action-secondary', + 'save', _('Save')); } } -- cgit v1.2.3-54-g00ecf From 61f71a4a597bb2eab2b56a80a71e867c1539739d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 11 Jan 2010 22:54:46 +0000 Subject: Updated markup for application registration and view links --- actions/apps.php | 7 +++++-- actions/showapplication.php | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/actions/apps.php b/actions/apps.php index e6500599f..7c7b24570 100644 --- a/actions/apps.php +++ b/actions/apps.php @@ -114,13 +114,16 @@ class AppsAction extends SettingsAction } } + $this->elementStart('p', array('id' => 'application_register')); $this->element('a', array('href' => common_local_url( 'newapplication', array('nickname' => $user->nickname) - ) + ), + 'class' => 'more' ), - 'Register a new application »'); + 'Register a new application'); + $this->elementEnd('p'); $this->pagination( $this->page > 1, diff --git a/actions/showapplication.php b/actions/showapplication.php index 5156fa6f0..3e191148a 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -249,16 +249,16 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementEnd('div'); - $this->elementStart('div', 'entity-list-apps'); + $this->elementStart('p', array('id' => 'application_action')); $this->element('a', array( 'href' => common_local_url( 'apps', - array('nickname' => $this->owner->nickname) - ) + array('nickname' => $this->owner->nickname)), + 'class' => 'more' ), 'View your applications'); - $this->elementEnd('div'); + $this->elementEnd('p'); } function resetKey() -- cgit v1.2.3-54-g00ecf From c8a4d0d6c2cafe1d3d4285c4b634f1dc52e91d8b Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 11 Jan 2010 23:51:12 +0000 Subject: Updated markup for application details --- actions/showapplication.php | 70 ++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/actions/showapplication.php b/actions/showapplication.php index 3e191148a..33bc51f93 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -152,7 +152,8 @@ class ShowApplicationAction extends OwnerDesignAction $cur = common_current_user(); $this->elementStart('div', 'entity_actions'); - + $this->elementStart('ul'); + $this->elementStart('li'); $this->element('a', array('href' => common_local_url( @@ -163,7 +164,9 @@ class ShowApplicationAction extends OwnerDesignAction ) ) ), 'Edit application'); + $this->elementEnd('li'); + $this->elementStart('li'); $this->elementStart('form', array( 'id' => 'forma_reset_key', 'class' => 'form_reset_key', @@ -177,32 +180,39 @@ class ShowApplicationAction extends OwnerDesignAction $this->submit('reset', _('Reset Consumer key/secret')); $this->elementEnd('fieldset'); $this->elementEnd('form'); - + $this->elementEnd('li'); + $this->elementEnd('ul'); $this->elementEnd('div'); $consumer = $this->application->getConsumer(); - $this->elementStart('div', 'entity-application'); - - $this->elementStart('ul', 'entity_application_details'); - - $this->elementStart('li', 'entity_application-icon'); - - if (!empty($this->application->icon)) { - $this->element('img', array('src' => $this->application->icon)); - } - - $this->elementEnd('li'); + $this->elementStart('div', 'entity_application'); + $this->element('h2', null, _('Application profile')); + $this->elementStart('dl', 'entity_depiction'); + $this->element('dt', null, _('Icon')); + $this->elementStart('dd'); + if (!empty($this->application->icon)) { + $this->element('img', array('src' => $this->application->icon)); + } + $this->elementEnd('dd'); + $this->elementEnd('dl'); - $this->elementStart('li', 'entity_application_name'); - $this->element('span', array('class' => 'big'), $this->application->name); + $this->elementStart('dl', 'entity_fn'); + $this->element('dt', null, _('Name')); + $this->elementStart('dd'); + $this->element('span', null, $this->application->name); $this->raw(sprintf(_(' by %1$s'), $this->application->organization)); - $this->elementEnd('li'); - - $this->element('li', 'entity_application_description', $this->application->description); + $this->elementEnd('dd'); + $this->elementEnd('dl'); - $this->elementStart('li', 'entity_application_statistics'); + $this->elementStart('dl', 'entity_note'); + $this->element('dt', null, _('Description')); + $this->element('dd', null, $this->application->description); + $this->elementEnd('dl'); + $this->elementStart('dl', 'entity_statistics'); + $this->element('dt', null, _('Statistics')); + $this->elementStart('dd'); $defaultAccess = ($this->application->access_type & Oauth_application::$writeAccess) ? 'read-write' : 'read-only'; $profile = Profile::staticGet($this->application->owner); @@ -214,39 +224,39 @@ class ShowApplicationAction extends OwnerDesignAction $defaultAccess, $userCnt )); + $this->elementEnd('dd'); + $this->elementEnd('dl'); + $this->elementEnd('div'); - $this->elementEnd('li'); - - $this->elementEnd('ul'); - + $this->elementStart('div', array('id' => 'entity_data')); + $this->element('h2', null, _('Application info')); $this->elementStart('dl', 'entity_consumer_key'); $this->element('dt', null, _('Consumer key')); - $this->element('dd', 'label', $consumer->consumer_key); + $this->element('dd', null, $consumer->consumer_key); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_consumer_secret'); $this->element('dt', null, _('Consumer secret')); - $this->element('dd', 'label', $consumer->consumer_secret); + $this->element('dd', null, $consumer->consumer_secret); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_request_token_url'); $this->element('dt', null, _('Request token URL')); - $this->element('dd', 'label', common_local_url('apioauthrequesttoken')); + $this->element('dd', null, common_local_url('apioauthrequesttoken')); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_access_token_url'); $this->element('dt', null, _('Access token URL')); - $this->element('dd', 'label', common_local_url('apioauthaccesstoken')); + $this->element('dd', null, common_local_url('apioauthaccesstoken')); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_authorize_url'); $this->element('dt', null, _('Authorize URL')); - $this->element('dd', 'label', common_local_url('apioauthauthorize')); + $this->element('dd', null, common_local_url('apioauthauthorize')); $this->elementEnd('dl'); $this->element('p', 'oauth-signature-note', - '*We support hmac-sha1 signatures. We do not support the plaintext signature method.'); - + '* We support hmac-sha1 signatures. We do not support the plaintext signature method.'); $this->elementEnd('div'); $this->elementStart('p', array('id' => 'application_action')); -- cgit v1.2.3-54-g00ecf From 0b90f7645e5bd54c99c6fb7c1fe105e183b9e8c1 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:01:45 +0000 Subject: Updated class for application list --- lib/applicationlist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index e305437f4..23c727bd6 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -69,7 +69,7 @@ class ApplicationList extends Widget function show() { - $this->out->elementStart('ul', 'applications xoxo'); + $this->out->elementStart('ul', array('id' => 'applications')); $cnt = 0; -- cgit v1.2.3-54-g00ecf From c2ffd6612887675e55a9d9398517e23ee95c9117 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:02:25 +0000 Subject: Updated markup for application details page. Similar to user/group profile page. --- actions/showapplication.php | 90 +++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/actions/showapplication.php b/actions/showapplication.php index 33bc51f93..db28395c2 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -151,63 +151,33 @@ class ShowApplicationAction extends OwnerDesignAction $cur = common_current_user(); - $this->elementStart('div', 'entity_actions'); - $this->elementStart('ul'); - $this->elementStart('li'); - $this->element('a', - array('href' => - common_local_url( - 'editapplication', - array( - 'nickname' => $this->owner->nickname, - 'id' => $this->application->id - ) - ) - ), 'Edit application'); - $this->elementEnd('li'); - - $this->elementStart('li'); - $this->elementStart('form', array( - 'id' => 'forma_reset_key', - 'class' => 'form_reset_key', - 'method' => 'POST', - 'action' => common_local_url('showapplication', - array('nickname' => $cur->nickname, - 'id' => $this->application->id)))); - - $this->elementStart('fieldset'); - $this->hidden('token', common_session_token()); - $this->submit('reset', _('Reset Consumer key/secret')); - $this->elementEnd('fieldset'); - $this->elementEnd('form'); - $this->elementEnd('li'); - $this->elementEnd('ul'); - $this->elementEnd('div'); - $consumer = $this->application->getConsumer(); - $this->elementStart('div', 'entity_application'); + $this->elementStart('div', 'entity_profile vcard'); $this->element('h2', null, _('Application profile')); $this->elementStart('dl', 'entity_depiction'); $this->element('dt', null, _('Icon')); $this->elementStart('dd'); if (!empty($this->application->icon)) { - $this->element('img', array('src' => $this->application->icon)); + $this->element('img', array('src' => $this->application->icon, + 'class' => 'photo logo')); } $this->elementEnd('dd'); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_fn'); $this->element('dt', null, _('Name')); - $this->elementStart('dd'); - $this->element('span', null, $this->application->name); - $this->raw(sprintf(_(' by %1$s'), $this->application->organization)); - $this->elementEnd('dd'); + $this->element('dd', 'fn', $this->application->name); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_org'); + $this->element('dt', null, _('Organization')); + $this->element('dd', 'org', $this->application->organization); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_note'); $this->element('dt', null, _('Description')); - $this->element('dd', null, $this->application->description); + $this->element('dd', 'note', $this->application->description); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_statistics'); @@ -228,7 +198,41 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementEnd('dl'); $this->elementEnd('div'); - $this->elementStart('div', array('id' => 'entity_data')); + $this->elementStart('div', 'entity_actions'); + $this->element('h2', null, _('Application actions')); + $this->elementStart('ul'); + $this->elementStart('li', 'entity_edit'); + $this->element('a', + array('href' => + common_local_url( + 'editapplication', + array( + 'nickname' => $this->owner->nickname, + 'id' => $this->application->id + ) + ) + ), 'Edit'); + $this->elementEnd('li'); + + $this->elementStart('li', 'entity_reset_keysecret'); + $this->elementStart('form', array( + 'id' => 'forma_reset_key', + 'class' => 'form_reset_key', + 'method' => 'POST', + 'action' => common_local_url('showapplication', + array('nickname' => $cur->nickname, + 'id' => $this->application->id)))); + + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->submit('reset', _('Reset key & secret')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->elementEnd('div'); + + $this->elementStart('div', 'entity_data'); $this->element('h2', null, _('Application info')); $this->elementStart('dl', 'entity_consumer_key'); $this->element('dt', null, _('Consumer key')); @@ -255,8 +259,8 @@ class ShowApplicationAction extends OwnerDesignAction $this->element('dd', null, common_local_url('apioauthauthorize')); $this->elementEnd('dl'); - $this->element('p', 'oauth-signature-note', - '* We support hmac-sha1 signatures. We do not support the plaintext signature method.'); + $this->element('p', 'note', + _('Note: We support hmac-sha1 signatures. We do not support the plaintext signature method.')); $this->elementEnd('div'); $this->elementStart('p', array('id' => 'application_action')); -- cgit v1.2.3-54-g00ecf From ba0c82b391ad3ec71bb7efe60b03f4f95f9ecaf5 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:13:36 +0000 Subject: Added anchors to application source and homepage --- actions/showapplication.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/actions/showapplication.php b/actions/showapplication.php index db28395c2..f2ff8b900 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -167,12 +167,20 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementStart('dl', 'entity_fn'); $this->element('dt', null, _('Name')); - $this->element('dd', 'fn', $this->application->name); + $this->elementStart('dd'); + $this->element('a', array('href' => $this->application->source_url, + 'class' => 'url fn'), + $this->application->name); + $this->elementEnd('dd'); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_org'); $this->element('dt', null, _('Organization')); - $this->element('dd', 'org', $this->application->organization); + $this->elementStart('dd'); + $this->element('a', array('href' => $this->application->homepage, + 'class' => 'url'), + $this->application->organization); + $this->elementEnd('dd'); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_note'); -- cgit v1.2.3-54-g00ecf From d998c4b1b89492e02d56ba8a395387078472f163 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:29:09 +0000 Subject: Fixed tabbing --- lib/applicationlist.php | 99 ++++++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 55 deletions(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 23c727bd6..b404767ba 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -69,7 +69,7 @@ class ApplicationList extends Widget function show() { - $this->out->elementStart('ul', array('id' => 'applications')); + $this->out->elementStart('ul', 'applications'); $cnt = 0; @@ -94,69 +94,58 @@ class ApplicationList extends Widget $this->out->elementStart('li', array('class' => 'application', 'id' => 'oauthclient-' . $this->application->id)); - if (!empty($this->application->icon)) { - $this->out->element('img', array('src' => $this->application->icon)); - } - - if (!$this->connections) { - - $this->out->elementStart('a', - array('href' => - common_local_url('showapplication', - array('nickname' => $user->nickname, - 'id' => $this->application->id)), - 'class' => 'url') - ); - - $this->out->raw($this->application->name); - $this->out->elementEnd('a'); - } else { - $this->out->elementStart('a', - array('href' => $this->application->source_url, - 'class' => 'url')); - - $this->out->raw($this->application->name); - $this->out->elementEnd('a'); - } - - $this->out->raw(' by '); - - $this->out->elementStart('a', - array( - 'href' => $this->application->homepage, - 'class' => 'url' - ) - ); - $this->out->raw($this->application->organization); - $this->out->elementEnd('a'); - - $this->out->elementStart('p', 'note'); - $this->out->raw($this->application->description); - $this->out->elementEnd('p'); + if (!empty($this->application->icon)) { + $this->out->element('img', array('src' => $this->application->icon)); + } + + if (!$this->connections) { + $this->out->elementStart('a', + array('href' => common_local_url('showapplication', + array('nickname' => $user->nickname, + 'id' => $this->application->id)), + 'class' => 'url')); + + $this->out->raw($this->application->name); + $this->out->elementEnd('a'); + } else { + $this->out->elementStart('a', array('href' => $this->application->source_url, + 'class' => 'url')); + + $this->out->raw($this->application->name); + $this->out->elementEnd('a'); + } - $this->out->elementEnd('li'); + $this->out->raw(' by '); - if ($this->connections) { + $this->out->elementStart('a', array('href' => $this->application->homepage, + 'class' => 'url')); + $this->out->raw($this->application->organization); + $this->out->elementEnd('a'); - $appUser = Oauth_application_user::getByKeys($this->owner, $this->application); + $this->out->elementStart('p', 'note'); + $this->out->raw($this->application->description); + $this->out->elementEnd('p'); - if (empty($appUser)) { - common_debug("empty appUser!"); - } + if ($this->connections) { + $appUser = Oauth_application_user::getByKeys($this->owner, $this->application); - $this->out->elementStart('li'); + if (empty($appUser)) { + common_debug("empty appUser!"); + } - $access = ($this->application->access_type & Oauth_application::$writeAccess) - ? 'read-write' : 'read-only'; + $this->out->elementStart('li'); - $txt = 'Approved ' . common_exact_date($appUser->modified) . - " $access for access."; + $access = ($this->application->access_type & Oauth_application::$writeAccess) + ? 'read-write' : 'read-only'; - $this->out->raw($txt); - $this->out->elementEnd('li'); + $txt = 'Approved ' . common_exact_date($appUser->modified) . + " $access for access."; - // XXX: Add revoke access button - } + $this->out->raw($txt); + $this->out->elementEnd('li'); + + // XXX: Add revoke access button + } } /* Override this in subclasses. */ -- cgit v1.2.3-54-g00ecf From 8e91e053923f395d4dc0ad7f3f328953c9b17145 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 11 Jan 2010 17:30:56 -0800 Subject: Make API auth handle OAuth requests w/access tokens --- lib/apiauth.php | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/lib/apiauth.php b/lib/apiauth.php index 7102764cb..3229ab19f 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -28,7 +28,7 @@ * @author Evan Prodromou * @author mEDI * @author Sarven Capadisli - * @author Zach Copley + * @author Zach Copley * @copyright 2009 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ @@ -39,6 +39,7 @@ if (!defined('STATUSNET')) { } require_once INSTALLDIR . '/lib/api.php'; +require_once INSTALLDIR . '/lib/apioauthstore.php'; /** * Actions extending this class will require auth @@ -52,6 +53,8 @@ require_once INSTALLDIR . '/lib/api.php'; class ApiAuthAction extends ApiAction { + var $access_token; + var $oauth_access_type; /** * Take arguments for running, and output basic auth header if needed @@ -67,12 +70,119 @@ class ApiAuthAction extends ApiAction parent::prepare($args); if ($this->requiresAuth()) { - $this->checkBasicAuthUser(); + + $this->consumer_key = $this->arg('oauth_consumer_key'); + $this->access_token = $this->arg('oauth_token'); + + if (!empty($this->access_token)) { + $this->checkOAuthRequest(); + } else { + $this->checkBasicAuthUser(); + } } return true; } + function checkOAuthRequest() + { + common_debug("We have an OAuth request."); + + $datastore = new ApiStatusNetOAuthDataStore(); + $server = new OAuthServer($datastore); + $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + + $server->add_signature_method($hmac_method); + + $this->cleanRequest(); + + try { + + $req = OAuthRequest::from_request(); + $server->verify_request($req); + + common_debug("Good OAuth request!"); + + $app = Oauth_application::getByConsumerKey($this->consumer_key); + + if (empty($app)) { + + // this should really not happen + common_log(LOG_WARN, + "Couldn't find the OAuth app for consumer key: $this->consumer_key"); + + throw new OAuthException('No application for that consumer key.'); + } + + $appUser = Oauth_application_user::staticGet('token', + $this->access_token); + + // XXX: check that app->id and appUser->application_id and consumer all + // match? + + if (!empty($appUser)) { + + // read or read-write + $this->oauth_access_type = $appUser->access_type; + + // If access_type == 0 we have either a request token + // or a bad / revoked access token + + if ($this->oauth_access_type != 0) { + + $this->auth_user = User::staticGet('id', $appUser->profile_id); + + $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . + "application '%s' (id: %d)."; + + common_log(LOG_INFO, sprintf($msg, + $this->auth_user->nickname, + $this->auth_user->id, + $app->name, + $app->id)); + return true; + } else { + throw new OAuthException('Bad access token.'); + } + } else { + + // also should not happen + throw new OAuthException('No user for that token.'); + } + + } catch (OAuthException $e) { + common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); + common_debug(var_export($req, true)); + $this->showOAuthError($e->getMessage()); + exit(); + } + } + + function showOAuthError($msg) + { + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/html; charset=utf-8'); + print $msg . "\n"; + } + + function cleanRequest() + { + // kill evil effects of magical slashing + + if(get_magic_quotes_gpc() == 1) { + $_POST = array_map('stripslashes', $_POST); + $_GET = array_map('stripslashes', $_GET); + } + + // strip out the p param added in index.php + + // XXX: should we strip anything else? Or alternatively + // only allow a known list of params? + + unset($_GET['p']); + unset($_POST['p']); + } + /** * Does this API resource require authentication? * -- cgit v1.2.3-54-g00ecf From 40c6d09c9f3a3a1167015782ac4972681ce98772 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:36:08 +0000 Subject: Added missing end tag --- lib/applicationlist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index b404767ba..b1dcc39a9 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -110,7 +110,6 @@ class ApplicationList extends Widget } else { $this->out->elementStart('a', array('href' => $this->application->source_url, 'class' => 'url')); - $this->out->raw($this->application->name); $this->out->elementEnd('a'); } @@ -125,6 +124,7 @@ class ApplicationList extends Widget $this->out->elementStart('p', 'note'); $this->out->raw($this->application->description); $this->out->elementEnd('p'); + $this->out->elementEnd('li'); if ($this->connections) { $appUser = Oauth_application_user::getByKeys($this->owner, $this->application); -- cgit v1.2.3-54-g00ecf From 34cc03c61792792e9b23e9a28e7730268b22d3d1 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:41:38 +0000 Subject: Moved application image inside the anchor --- lib/applicationlist.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index b1dcc39a9..8961da435 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -94,10 +94,6 @@ class ApplicationList extends Widget $this->out->elementStart('li', array('class' => 'application', 'id' => 'oauthclient-' . $this->application->id)); - if (!empty($this->application->icon)) { - $this->out->element('img', array('src' => $this->application->icon)); - } - if (!$this->connections) { $this->out->elementStart('a', array('href' => common_local_url('showapplication', @@ -105,15 +101,18 @@ class ApplicationList extends Widget 'id' => $this->application->id)), 'class' => 'url')); - $this->out->raw($this->application->name); - $this->out->elementEnd('a'); } else { $this->out->elementStart('a', array('href' => $this->application->source_url, 'class' => 'url')); - $this->out->raw($this->application->name); - $this->out->elementEnd('a'); } + if (!empty($this->application->icon)) { + $this->out->element('img', array('src' => $this->application->icon)); + } + + $this->out->raw($this->application->name); + $this->out->elementEnd('a'); + $this->out->raw(' by '); $this->out->elementStart('a', array('href' => $this->application->homepage, -- cgit v1.2.3-54-g00ecf From 276c4a2a231cce0b449e97d2bdba0a822b898e08 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:44:15 +0000 Subject: Added vcard and photo classes --- lib/applicationlist.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 8961da435..6ca210537 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -94,6 +94,7 @@ class ApplicationList extends Widget $this->out->elementStart('li', array('class' => 'application', 'id' => 'oauthclient-' . $this->application->id)); + $this->out->elementStart('span', 'vcard author'); if (!$this->connections) { $this->out->elementStart('a', array('href' => common_local_url('showapplication', @@ -107,11 +108,13 @@ class ApplicationList extends Widget } if (!empty($this->application->icon)) { - $this->out->element('img', array('src' => $this->application->icon)); + $this->out->element('img', array('src' => $this->application->icon, + 'class' => 'photo')); } $this->out->raw($this->application->name); $this->out->elementEnd('a'); + $this->out->elementEnd('span'); $this->out->raw(' by '); -- cgit v1.2.3-54-g00ecf From a009052036250d33b38329dd0363dd42652e89da Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:52:59 +0000 Subject: A little minimization --- lib/applicationlist.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 6ca210537..15c2d588a 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -109,23 +109,20 @@ class ApplicationList extends Widget if (!empty($this->application->icon)) { $this->out->element('img', array('src' => $this->application->icon, - 'class' => 'photo')); + 'class' => 'photo avatar')); } - $this->out->raw($this->application->name); + $this->out->element('span', 'fn', $this->application->name); $this->out->elementEnd('a'); $this->out->elementEnd('span'); $this->out->raw(' by '); - $this->out->elementStart('a', array('href' => $this->application->homepage, - 'class' => 'url')); - $this->out->raw($this->application->organization); - $this->out->elementEnd('a'); + $this->out->element('a', array('href' => $this->application->homepage, + 'class' => 'url'), + $this->application->organization); - $this->out->elementStart('p', 'note'); - $this->out->raw($this->application->description); - $this->out->elementEnd('p'); + $this->out->element('p', 'note', $this->application->description); $this->out->elementEnd('li'); if ($this->connections) { -- cgit v1.2.3-54-g00ecf From 8d02a897dc6269b44d529938b2c6298e19a2ec59 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 02:50:54 +0000 Subject: Updated markup for application edit form; image, radios --- lib/applicationeditform.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index f8fcb3e3f..e9ab46780 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -175,7 +175,7 @@ class ApplicationEditForm extends Form $this->out->elementStart('ul', 'form_data'); - $this->out->elementStart('li'); + $this->out->elementStart('li', array('id' => 'application_icon')); if (!empty($icon)) { $this->out->element('img', array('src' => $icon)); @@ -193,7 +193,7 @@ class ApplicationEditForm extends Form 'value' => ImageFile::maxFileSizeInt())); $this->out->elementEnd('li'); - $this->out->elementStart('li'); + $this->out->elementStart('li'); $this->out->hidden('application_id', $id); @@ -241,7 +241,7 @@ class ApplicationEditForm extends Form _('URL to redirect to after authentication')); $this->out->elementEnd('li'); - $this->out->elementStart('li'); + $this->out->elementStart('li', array('id' => 'application_types')); $attrs = array('name' => 'app_type', 'type' => 'radio', @@ -280,7 +280,7 @@ class ApplicationEditForm extends Form $this->out->element('p', 'form_guide', _('Type of application, browser or desktop')); $this->out->elementEnd('li'); - $this->out->elementStart('li'); + $this->out->elementStart('li', array('id' => 'default_access_types')); $attrs = array('name' => 'default_access_type', 'type' => 'radio', -- cgit v1.2.3-54-g00ecf From 7694955cd654becf8f03dc4eca271d8188141596 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 01:16:42 +0000 Subject: Callback URL can be null --- db/statusnet.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/statusnet.sql b/db/statusnet.sql index c3160bcf8..b1867e7f0 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -218,7 +218,7 @@ create table oauth_application ( source_url varchar(255) comment 'application homepage - used for source link', organization varchar(255) comment 'name of the organization running the application', homepage varchar(255) comment 'homepage for the organization', - callback_url varchar(255) not null comment 'url to redirect to after authentication', + callback_url varchar(255) comment 'url to redirect to after authentication', type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop', access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write', created datetime not null comment 'date this record was created', -- cgit v1.2.3-54-g00ecf From adfca0180847571b9474db76a0c4daa407acf22b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 01:22:37 +0000 Subject: Can now edit/change application icon --- actions/editapplication.php | 128 ++++++++++++++++++++---------------------- actions/newapplication.php | 85 +++++++++------------------- classes/Oauth_application.php | 53 ++++++++++++++--- 3 files changed, 130 insertions(+), 136 deletions(-) diff --git a/actions/editapplication.php b/actions/editapplication.php index 6b8dd501c..a0ed3117a 100644 --- a/actions/editapplication.php +++ b/actions/editapplication.php @@ -93,47 +93,47 @@ class EditApplicationAction extends OwnerDesignAction parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $this->handlePost($args); - } else { - $this->showForm(); - } + $this->handlePost($args); + } else { + $this->showForm(); + } } function handlePost($args) { - // Workaround for PHP returning empty $_POST and $_FILES when POST + // Workaround for PHP returning empty $_POST and $_FILES when POST // length > post_max_size in php.ini if (empty($_FILES) && empty($_POST) && ($_SERVER['CONTENT_LENGTH'] > 0) - ) { + ) { $msg = _('The server was unable to handle that much POST ' . - 'data (%s bytes) due to its current configuration.'); + 'data (%s bytes) due to its current configuration.'); $this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH'])); return; } - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->clientError(_('There was a problem with your session token.')); - return; - } - - $cur = common_current_user(); - - if ($this->arg('cancel')) { - common_redirect(common_local_url('showapplication', - array( - 'nickname' => $cur->nickname, - 'id' => $this->app->id) - ), 303); - } elseif ($this->arg('save')) { - $this->trySave(); - } else { - $this->clientError(_('Unexpected form submission.')); - } + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } + + $cur = common_current_user(); + + if ($this->arg('cancel')) { + common_redirect(common_local_url('showapplication', + array( + 'nickname' => $cur->nickname, + 'id' => $this->app->id) + ), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } } function showForm($msg=null) @@ -170,8 +170,8 @@ class EditApplicationAction extends OwnerDesignAction $access_type = $this->arg('default_access_type'); if (empty($name)) { - $this->showForm(_('Name is required.')); - return; + $this->showForm(_('Name is required.')); + return; } elseif (mb_strlen($name) > 255) { $this->showForm(_('Name is too long (max 255 chars).')); return; @@ -181,20 +181,17 @@ class EditApplicationAction extends OwnerDesignAction } elseif (Oauth_application::descriptionTooLong($description)) { $this->showForm(sprintf( _('Description is too long (max %d chars).'), - Oauth_application::maxDescription())); + Oauth_application::maxDescription())); return; - } elseif (empty($source_url)) { - $this->showForm(_('Source URL is required.')); - return; - } elseif ((strlen($source_url) > 0) - && !Validate::uri( - $source_url, - array('allowed_schemes' => array('http', 'https')) - ) - ) - { - $this->showForm(_('Source URL is not valid.')); + } elseif (mb_strlen($source_url) > 255) { + $this->showForm(_('Source URL is too long.')); return; + } elseif ((mb_strlen($source_url) > 0) + && !Validate::uri($source_url, + array('allowed_schemes' => array('http', 'https')))) + { + $this->showForm(_('Source URL is not valid.')); + return; } elseif (empty($organization)) { $this->showForm(_('Organization is required.')); return; @@ -204,35 +201,30 @@ class EditApplicationAction extends OwnerDesignAction } elseif (empty($homepage)) { $this->showForm(_('Organization homepage is required.')); return; - } elseif ((strlen($homepage) > 0) - && !Validate::uri( - $homepage, - array('allowed_schemes' => array('http', 'https')) - ) - ) - { - $this->showForm(_('Homepage is not a valid URL.')); - return; - } elseif (empty($callback_url)) { - $this->showForm(_('Callback is required.')); - return; - } elseif (strlen($callback_url) > 0 - && !Validate::uri( - $source_url, - array('allowed_schemes' => array('http', 'https')) - ) - ) - { - $this->showForm(_('Callback URL is not valid.')); - return; - } + } elseif ((mb_strlen($homepage) > 0) + && !Validate::uri($homepage, + array('allowed_schemes' => array('http', 'https')))) + { + $this->showForm(_('Homepage is not a valid URL.')); + return; + } elseif (mb_strlen($callback_url) > 255) { + $this->showForm(_('Callback is too long.')); + return; + } elseif (mb_strlen($callback_url) > 0 + && !Validate::uri($source_url, + array('allowed_schemes' => array('http', 'https')) + )) + { + $this->showForm(_('Callback URL is not valid.')); + return; + } $cur = common_current_user(); // Checked in prepare() above assert(!is_null($cur)); - assert(!is_null($this->app)); + assert(!is_null($this->app)); $orig = clone($this->app); @@ -244,9 +236,7 @@ class EditApplicationAction extends OwnerDesignAction $this->app->callback_url = $callback_url; $this->app->type = $type; - $result = $this->app->update($orig); - - common_debug("access_type = $access_type"); + common_debug("access_type = $access_type"); if ($access_type == 'r') { $this->app->access_type = 1; @@ -254,11 +244,15 @@ class EditApplicationAction extends OwnerDesignAction $this->app->access_type = 3; } + $result = $this->app->update($orig); + if (!$result) { common_log_db_error($this->app, 'UPDATE', __FILE__); $this->serverError(_('Could not update application.')); } + $this->app->uploadLogo(); + common_redirect(common_local_url('apps', array('nickname' => $cur->nickname)), 303); } diff --git a/actions/newapplication.php b/actions/newapplication.php index a0e61d288..3d42b657b 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -83,7 +83,7 @@ class NewApplicationAction extends OwnerDesignAction parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $this->handlePost($args); + $this->handlePost($args); } else { $this->showForm(); } @@ -91,36 +91,36 @@ class NewApplicationAction extends OwnerDesignAction function handlePost($args) { - // Workaround for PHP returning empty $_POST and $_FILES when POST + // Workaround for PHP returning empty $_POST and $_FILES when POST // length > post_max_size in php.ini if (empty($_FILES) && empty($_POST) && ($_SERVER['CONTENT_LENGTH'] > 0) - ) { + ) { $msg = _('The server was unable to handle that much POST ' . - 'data (%s bytes) due to its current configuration.'); + 'data (%s bytes) due to its current configuration.'); $this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH'])); return; } - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->clientError(_('There was a problem with your session token.')); - return; - } - - $cur = common_current_user(); - - if ($this->arg('cancel')) { - common_redirect(common_local_url('apps', - array('nickname' => $cur->nickname)), 303); - } elseif ($this->arg('save')) { - $this->trySave(); - } else { - $this->clientError(_('Unexpected form submission.')); - } + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } + + $cur = common_current_user(); + + if ($this->arg('cancel')) { + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } } function showForm($msg=null) @@ -147,7 +147,7 @@ class NewApplicationAction extends OwnerDesignAction function trySave() { - $name = $this->trimmed('name'); + $name = $this->trimmed('name'); $description = $this->trimmed('description'); $source_url = $this->trimmed('source_url'); $organization = $this->trimmed('organization'); @@ -200,8 +200,8 @@ class NewApplicationAction extends OwnerDesignAction { $this->showForm(_('Homepage is not a valid URL.')); return; - } elseif (empty($callback_url)) { - $this->showForm(_('Callback is required.')); + } elseif (mb_strlen($callback_url) > 255) { + $this->showForm(_('Callback is too long.')); return; } elseif (strlen($callback_url) > 0 && !Validate::uri( @@ -266,7 +266,7 @@ class NewApplicationAction extends OwnerDesignAction $app->query('ROLLBACK'); } - $this->uploadLogo($app); + $this->app->uploadLogo(); $app->query('COMMIT'); @@ -275,40 +275,5 @@ class NewApplicationAction extends OwnerDesignAction } - /** - * Handle an image upload - * - * Does all the magic for handling an image upload, and crops the - * image by default. - * - * @return void - */ - - function uploadLogo($app) - { - if ($_FILES['app_icon']['error'] == - UPLOAD_ERR_OK) { - - try { - $imagefile = ImageFile::fromUpload('app_icon'); - } catch (Exception $e) { - common_debug("damn that sucks"); - $this->showForm($e->getMessage()); - return; - } - - $filename = Avatar::filename($app->id, - image_type_to_extension($imagefile->type), - null, - 'oauth-app-icon-'.common_timestamp()); - - $filepath = Avatar::path($filename); - - move_uploaded_file($imagefile->filepath, $filepath); - - $app->setOriginal($filename); - } - } - } diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php index 5df8b9459..a6b539087 100644 --- a/classes/Oauth_application.php +++ b/classes/Oauth_application.php @@ -27,7 +27,7 @@ class Oauth_application extends Memcached_DataObject /* Static get */ function staticGet($k,$v=NULL) { - return Memcached_DataObject::staticGet('Oauth_application',$k,$v); + return Memcached_DataObject::staticGet('Oauth_application',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE @@ -90,16 +90,51 @@ class Oauth_application extends Memcached_DataObject static function getByConsumerKey($key) { - if (empty($key)) { - return null; - } + if (empty($key)) { + return null; + } + + $app = new Oauth_application(); + $app->consumer_key = $key; + $app->limit(1); + $result = $app->find(true); + + return empty($result) ? null : $app; + } + + /** + * Handle an image upload + * + * Does all the magic for handling an image upload, and crops the + * image by default. + * + * @return void + */ - $app = new Oauth_application(); - $app->consumer_key = $key; - $app->limit(1); - $result = $app->find(true); + function uploadLogo() + { + if ($_FILES['app_icon']['error'] == + UPLOAD_ERR_OK) { - return empty($result) ? null : $app; + try { + $imagefile = ImageFile::fromUpload('app_icon'); + } catch (Exception $e) { + common_debug("damn that sucks"); + $this->showForm($e->getMessage()); + return; + } + + $filename = Avatar::filename($this->id, + image_type_to_extension($imagefile->type), + null, + 'oauth-app-icon-'.common_timestamp()); + + $filepath = Avatar::path($filename); + + move_uploaded_file($imagefile->filepath, $filepath); + + $this->setOriginal($filename); + } } } -- cgit v1.2.3-54-g00ecf From 8da5e98cba12c32f0b75a90d1ff0007b73f0fc8d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 05:06:35 +0000 Subject: OAuth 1.0 working now --- actions/apioauthaccesstoken.php | 40 +++++------- actions/apioauthauthorize.php | 111 +++++++++++++++++++------------ actions/apioauthrequesttoken.php | 24 +++++-- lib/apiauth.php | 138 +++++++++++++++++---------------------- lib/apioauth.php | 122 ++++++++++++++++++++++++++++++++++ lib/apioauthstore.php | 69 +++++++++++--------- lib/router.php | 11 +++- 7 files changed, 330 insertions(+), 185 deletions(-) create mode 100644 lib/apioauth.php diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php index 67359d765..085ef6f0b 100644 --- a/actions/apioauthaccesstoken.php +++ b/actions/apioauthaccesstoken.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/apioauthstore.php'; +require_once INSTALLDIR . '/lib/apioauth.php'; /** * Exchange an authorized OAuth request token for an access token @@ -43,19 +43,9 @@ require_once INSTALLDIR . '/lib/apioauthstore.php'; * @link http://status.net/ */ -class ApiOauthAccessTokenAction extends Action +class ApiOauthAccessTokenAction extends ApiOauthAction { - /** - * Is read only? - * - * @return boolean false - */ - function isReadOnly() - { - return false; - } - /** * Class handler. * @@ -73,7 +63,7 @@ class ApiOauthAccessTokenAction extends Action $server->add_signature_method($hmac_method); - $atok = null; + $atok = null; try { $req = OAuthRequest::from_request(); @@ -81,24 +71,24 @@ class ApiOauthAccessTokenAction extends Action } catch (OAuthException $e) { common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); - common_debug(var_export($req, true)); - $this->outputError($e->getMessage()); - return; + common_debug(var_export($req, true)); + $this->outputError($e->getMessage()); + return; } - if (empty($atok)) { - common_debug('couldn\'t get access token.'); - print "Token exchange failed. Has the request token been authorized?\n"; - } else { - print $atok; - } + if (empty($atok)) { + common_debug('couldn\'t get access token.'); + print "Token exchange failed. Has the request token been authorized?\n"; + } else { + print $atok; + } } function outputError($msg) { - header('HTTP/1.1 401 Unauthorized'); - header('Content-Type: text/html; charset=utf-8'); - print $msg . "\n"; + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/html; charset=utf-8'); + print $msg . "\n"; } } diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index 48d5087ef..cdf9cb7df 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/apioauthstore.php'; +require_once INSTALLDIR . '/lib/apioauth.php'; /** * Authorize an OAuth request token @@ -43,7 +43,7 @@ require_once INSTALLDIR . '/lib/apioauthstore.php'; * @link http://status.net/ */ -class ApiOauthAuthorizeAction extends Action +class ApiOauthAuthorizeAction extends ApiOauthAction { var $oauth_token; var $callback; @@ -67,7 +67,7 @@ class ApiOauthAuthorizeAction extends Action { parent::prepare($args); - common_debug(var_export($_REQUEST, true)); + common_debug("apioauthauthorize"); $this->nickname = $this->trimmed('nickname'); $this->password = $this->arg('password'); @@ -130,7 +130,7 @@ class ApiOauthAuthorizeAction extends Action } else { - // XXX: make better error messages + // XXX: make better error messages if (empty($this->oauth_token)) { @@ -145,7 +145,8 @@ class ApiOauthAuthorizeAction extends Action return; } - common_debug("Requesting auth for app: $app->name."); + $name = $this->app->name; + common_debug("Requesting auth for app: " . $name); $this->showForm(); } @@ -153,6 +154,8 @@ class ApiOauthAuthorizeAction extends Action function handlePost() { + common_debug("handlePost()"); + // check session token for CSRF protection. $token = $this->trimmed('token'); @@ -170,7 +173,7 @@ class ApiOauthAuthorizeAction extends Action // check creds - $user = null; + $user = null; if (!common_logged_in()) { $user = common_check_user($this->nickname, $this->password); @@ -179,64 +182,86 @@ class ApiOauthAuthorizeAction extends Action return; } } else { - $user = common_current_user(); - } + $user = common_current_user(); + } if ($this->arg('allow')) { - // mark the req token as authorized + // mark the req token as authorized $this->store->authorize_token($this->oauth_token); - // Check to see if there was a previous token associated - // with this user/app and kill it. If you're doing this you - // probably don't want any old tokens anyway. + // Check to see if there was a previous token associated + // with this user/app and kill it. If the user is doing this she + // probably doesn't want any old tokens anyway. + + $appUser = Oauth_application_user::getByKeys($user, $this->app); + + if (!empty($appUser)) { + $result = $appUser->delete(); - $appUser = Oauth_application_user::getByKeys($user, $this->app); + if (!$result) { + common_log_db_error($appUser, 'DELETE', __FILE__); + throw new ServerException(_('DB error deleting OAuth app user.')); + return; + } + } - if (!empty($appUser)) { - $result = $appUser->delete(); + // associated the authorized req token with the user and the app - if (!$result) { - common_log_db_error($appUser, 'DELETE', __FILE__); - throw new ServerException(_('DB error deleting OAuth app user.')); - return; - } - } + $appUser = new Oauth_application_user(); - // associated the new req token with the user and the app + $appUser->profile_id = $user->id; + $appUser->application_id = $this->app->id; - $appUser = new Oauth_application_user(); + // Note: do not copy the access type from the application. + // The access type should always be 0 when the OAuth app + // user record has a request token associated with it. + // Access type gets assigned once an access token has been + // granted. The OAuth app user record then gets updated + // with the new access token and access type. - $appUser->profile_id = $user->id; - $appUser->application_id = $this->app->id; - $appUser->access_type = $this->app->access_type; - $appUser->token = $this->oauth_token; - $appUser->created = common_sql_now(); + $appUser->token = $this->oauth_token; + $appUser->created = common_sql_now(); - $result = $appUser->insert(); + $result = $appUser->insert(); - if (!$result) { - common_log_db_error($appUser, 'INSERT', __FILE__); - throw new ServerException(_('DB error inserting OAuth app user.')); - return; - } + if (!$result) { + common_log_db_error($appUser, 'INSERT', __FILE__); + throw new ServerException(_('DB error inserting OAuth app user.')); + return; + } // if we have a callback redirect and provide the token + // A callback specified in the app setup overrides whatever + // is passed in with the request. + + common_debug("Req token is authorized - doing callback"); + + if (!empty($this->app->callback_url)) { + $this->callback = $this->app->callback_url; + } + if (!empty($this->callback)) { - // XXX: Need better way to build this redirect url. + // XXX: Need better way to build this redirect url. + + $target_url = $this->getCallback($this->callback, + array('oauth_token' => $this->oauth_token)); + + common_debug("Doing callback to $target_url"); - $target_url = $this->callback . '?oauth_token=' . $this->oauth_token; common_redirect($target_url, 303); + } else { + common_debug("callback was empty!"); } // otherwise inform the user that the rt was authorized $this->elementStart('p'); - // XXX: Do OAuth 1.0a verifier code? + // XXX: Do OAuth 1.0a verifier code $this->raw(sprintf(_("The request token %s has been authorized. " . 'Please exchange it for an access token.'), @@ -267,9 +292,9 @@ class ApiOauthAuthorizeAction extends Action function showScripts() { parent::showScripts(); - if (!common_logged_in()) { - $this->autofocus('nickname'); - } + if (!common_logged_in()) { + $this->autofocus('nickname'); + } } /** @@ -313,9 +338,9 @@ class ApiOauthAuthorizeAction extends Action function showContent() { $this->elementStart('form', array('method' => 'post', - 'id' => 'form_login', - 'class' => 'form_settings', - 'action' => common_local_url('apioauthauthorize'))); + 'id' => 'form_login', + 'class' => 'form_settings', + 'action' => common_local_url('apioauthauthorize'))); $this->hidden('token', common_session_token()); $this->hidden('oauth_token', $this->oauth_token); diff --git a/actions/apioauthrequesttoken.php b/actions/apioauthrequesttoken.php index 53aca6b96..467640b9a 100644 --- a/actions/apioauthrequesttoken.php +++ b/actions/apioauthrequesttoken.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/apioauthstore.php'; +require_once INSTALLDIR . '/lib/apioauth.php'; /** * Get an OAuth request token @@ -43,16 +43,28 @@ require_once INSTALLDIR . '/lib/apioauthstore.php'; * @link http://status.net/ */ -class ApiOauthRequestTokenAction extends Action +class ApiOauthRequestTokenAction extends ApiOauthAction { /** - * Is read only? + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag * - * @return boolean false */ - function isReadOnly() + + function prepare($args) { - return false; + parent::prepare($args); + + $this->callback = $this->arg('oauth_callback'); + + if (!empty($this->callback)) { + common_debug("callback: $this->callback"); + } + + return true; } /** diff --git a/lib/apiauth.php b/lib/apiauth.php index 3229ab19f..431f3ac4f 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -39,7 +39,7 @@ if (!defined('STATUSNET')) { } require_once INSTALLDIR . '/lib/api.php'; -require_once INSTALLDIR . '/lib/apioauthstore.php'; +require_once INSTALLDIR . '/lib/apioauth.php'; /** * Actions extending this class will require auth @@ -71,14 +71,14 @@ class ApiAuthAction extends ApiAction if ($this->requiresAuth()) { - $this->consumer_key = $this->arg('oauth_consumer_key'); - $this->access_token = $this->arg('oauth_token'); + $this->consumer_key = $this->arg('oauth_consumer_key'); + $this->access_token = $this->arg('oauth_token'); - if (!empty($this->access_token)) { - $this->checkOAuthRequest(); - } else { - $this->checkBasicAuthUser(); - } + if (!empty($this->access_token)) { + $this->checkOAuthRequest(); + } else { + $this->checkBasicAuthUser(); + } } return true; @@ -86,101 +86,83 @@ class ApiAuthAction extends ApiAction function checkOAuthRequest() { - common_debug("We have an OAuth request."); + common_debug("We have an OAuth request."); - $datastore = new ApiStatusNetOAuthDataStore(); - $server = new OAuthServer($datastore); - $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + $datastore = new ApiStatusNetOAuthDataStore(); + $server = new OAuthServer($datastore); + $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); - $server->add_signature_method($hmac_method); + $server->add_signature_method($hmac_method); - $this->cleanRequest(); + ApiOauthAction::cleanRequest(); - try { + try { - $req = OAuthRequest::from_request(); - $server->verify_request($req); + $req = OAuthRequest::from_request(); + $server->verify_request($req); - common_debug("Good OAuth request!"); + common_debug("Good OAuth request!"); - $app = Oauth_application::getByConsumerKey($this->consumer_key); + $app = Oauth_application::getByConsumerKey($this->consumer_key); - if (empty($app)) { + if (empty($app)) { - // this should really not happen - common_log(LOG_WARN, - "Couldn't find the OAuth app for consumer key: $this->consumer_key"); + // this should really not happen + common_log(LOG_WARN, + "Couldn't find the OAuth app for consumer key: $this->consumer_key"); - throw new OAuthException('No application for that consumer key.'); - } + throw new OAuthException('No application for that consumer key.'); + } - $appUser = Oauth_application_user::staticGet('token', - $this->access_token); + $appUser = Oauth_application_user::staticGet('token', + $this->access_token); - // XXX: check that app->id and appUser->application_id and consumer all - // match? + // XXX: check that app->id and appUser->application_id and consumer all + // match? - if (!empty($appUser)) { + if (!empty($appUser)) { - // read or read-write - $this->oauth_access_type = $appUser->access_type; + // read or read-write + $this->oauth_access_type = $appUser->access_type; - // If access_type == 0 we have either a request token - // or a bad / revoked access token + // If access_type == 0 we have either a request token + // or a bad / revoked access token - if ($this->oauth_access_type != 0) { + if ($this->oauth_access_type != 0) { - $this->auth_user = User::staticGet('id', $appUser->profile_id); + $this->auth_user = User::staticGet('id', $appUser->profile_id); - $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . - "application '%s' (id: %d)."; + $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . + "application '%s' (id: %d)."; - common_log(LOG_INFO, sprintf($msg, - $this->auth_user->nickname, - $this->auth_user->id, - $app->name, - $app->id)); - return true; - } else { - throw new OAuthException('Bad access token.'); - } - } else { + common_log(LOG_INFO, sprintf($msg, + $this->auth_user->nickname, + $this->auth_user->id, + $app->name, + $app->id)); + return true; + } else { + throw new OAuthException('Bad access token.'); + } + } else { - // also should not happen - throw new OAuthException('No user for that token.'); - } + // also should not happen + throw new OAuthException('No user for that token.'); + } - } catch (OAuthException $e) { - common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); - common_debug(var_export($req, true)); - $this->showOAuthError($e->getMessage()); - exit(); - } + } catch (OAuthException $e) { + common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); + common_debug(var_export($req, true)); + $this->showOAuthError($e->getMessage()); + exit(); + } } function showOAuthError($msg) { - header('HTTP/1.1 401 Unauthorized'); - header('Content-Type: text/html; charset=utf-8'); - print $msg . "\n"; - } - - function cleanRequest() - { - // kill evil effects of magical slashing - - if(get_magic_quotes_gpc() == 1) { - $_POST = array_map('stripslashes', $_POST); - $_GET = array_map('stripslashes', $_GET); - } - - // strip out the p param added in index.php - - // XXX: should we strip anything else? Or alternatively - // only allow a known list of params? - - unset($_GET['p']); - unset($_POST['p']); + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/html; charset=utf-8'); + print $msg . "\n"; } /** diff --git a/lib/apioauth.php b/lib/apioauth.php new file mode 100644 index 000000000..4cb8a6775 --- /dev/null +++ b/lib/apioauth.php @@ -0,0 +1,122 @@ +. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apioauthstore.php'; + +/** + * Base action for API OAuth enpoints. Clean up the + * the request, and possibly some other common things + * here. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiOauthAction extends Action +{ + /** + * Is this a read-only action? + * + * @return boolean false + */ + + function isReadOnly($args) + { + return false; + } + + function prepare($args) + { + parent::prepare($args); + return true; + } + + /** + * Handle input, produce output + * + * Switches on request method; either shows the form or handles its input. + * + * @param array $args $_REQUEST data + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + self::cleanRequest(); + } + + static function cleanRequest() + { + // kill evil effects of magical slashing + + if (get_magic_quotes_gpc() == 1) { + $_POST = array_map('stripslashes', $_POST); + $_GET = array_map('stripslashes', $_GET); + } + + // strip out the p param added in index.php + + // XXX: should we strip anything else? Or alternatively + // only allow a known list of params? + + unset($_GET['p']); + unset($_POST['p']); + } + + function getCallback($url, $params) + { + foreach ($params as $k => $v) { + $url = $this->appendQueryVar($url, + OAuthUtil::urlencode_rfc3986($k), + OAuthUtil::urlencode_rfc3986($v)); + } + + return $url; + } + + function appendQueryVar($url, $k, $v) { + $url = preg_replace('/(.*)(\?|&)' . $k . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&'); + $url = substr($url, 0, -1); + if (strpos($url, '?') === false) { + return ($url . '?' . $k . '=' . $v); + } else { + return ($url . '&' . $k . '=' . $v); + } + } + +} diff --git a/lib/apioauthstore.php b/lib/apioauthstore.php index 290ce8973..c39ddbb0f 100644 --- a/lib/apioauthstore.php +++ b/lib/apioauthstore.php @@ -40,44 +40,44 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore { common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__); - $rt = new Token(); + $rt = new Token(); $rt->consumer_key = $consumer->key; $rt->tok = $token->key; $rt->type = 0; // request $app = Oauth_application::getByConsumerKey($consumer->key); - if (empty($app)) { - common_debug("empty app!"); - } + if (empty($app)) { + common_debug("empty app!"); + } - if ($rt->find(true) && $rt->state == 1) { // authorized + if ($rt->find(true) && $rt->state == 1) { // authorized common_debug('request token found.', __FILE__); - // find the associated user of the app + // find the associated user of the app - $appUser = new Oauth_application_user(); - $appUser->application_id = $app->id; - $appUser->token = $rt->tok; - $result = $appUser->find(true); + $appUser = new Oauth_application_user(); + $appUser->application_id = $app->id; + $appUser->token = $rt->tok; + $result = $appUser->find(true); - if (!empty($result)) { - common_debug("Oath app user found."); - } else { - common_debug("Oauth app user not found."); - return null; - } + if (!empty($result)) { + common_debug("Oath app user found."); + } else { + common_debug("Oauth app user not found."); + return null; + } - // go ahead and make the access token + // go ahead and make the access token - $at = new Token(); + $at = new Token(); $at->consumer_key = $consumer->key; $at->tok = common_good_rand(16); $at->secret = common_good_rand(16); $at->type = 1; // access $at->created = DB_DataObject_Cast::dateTime(); - if (!$at->insert()) { + if (!$at->insert()) { $e = $at->_lastError; common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__); return null; @@ -91,21 +91,30 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore } common_debug('request token "'.$rt->tok.'" updated', __FILE__); - // update the token from req to access for the user + // update the token from req to access for the user + + $orig = clone($appUser); + $appUser->token = $at->tok; - $orig = clone($appUser); - $appUser->token = $at->tok; - $result = $appUser->update($orig); + // It's at this point that we change the access type + // to whatever the application's access is. Request + // tokens should always have an access type of 0, and + // therefore be unuseable for making requests for + // protected resources. - if (empty($result)) { - common_debug('couldn\'t update OAuth app user.'); - return null; - } + $appUser->access_type = $app->access_type; + + $result = $appUser->update($orig); + + if (empty($result)) { + common_debug('couldn\'t update OAuth app user.'); + return null; + } - // Okay, good + // Okay, good - return new OAuthToken($at->tok, $at->secret); - } + return new OAuthToken($at->tok, $at->secret); + } } else { return null; diff --git a/lib/router.php b/lib/router.php index 420f5a0a1..d6e448c2f 100644 --- a/lib/router.php +++ b/lib/router.php @@ -50,8 +50,7 @@ class Router var $m = null; static $inst = null; static $bare = array('requesttoken', 'accesstoken', 'userauthorization', - 'postnotice', 'updateprofile', 'finishremotesubscribe', - 'apioauthrequesttoken', 'apioauthaccesstoken'); + 'postnotice', 'updateprofile', 'finishremotesubscribe'); static function get() { @@ -659,7 +658,13 @@ class Router 'id' => '[0-9]+') ); - $m->connect('oauth/authorize', + $m->connect('api/oauth/request_token', + array('action' => 'apioauthrequesttoken')); + + $m->connect('api/oauth/access_token', + array('action' => 'apioauthaccesstoken')); + + $m->connect('api/oauth/authorize', array('action' => 'apioauthauthorize')); foreach (array('subscriptions', 'subscribers') as $a) { -- cgit v1.2.3-54-g00ecf From 693b16174ad4142d1a543f78878c84c552ce6d74 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 05:31:48 +0000 Subject: Fix icon upload on new apps --- actions/newapplication.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/newapplication.php b/actions/newapplication.php index 3d42b657b..7bb81095d 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -266,7 +266,7 @@ class NewApplicationAction extends OwnerDesignAction $app->query('ROLLBACK'); } - $this->app->uploadLogo(); + $app->uploadLogo(); $app->query('COMMIT'); -- cgit v1.2.3-54-g00ecf From e101a6df6ba1cbec4664bb81fc81655e5db18b0f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 07:33:51 +0000 Subject: Rework application registration workflow to be more private --- actions/apps.php | 170 ----------------------------------- actions/editapplication.php | 8 +- actions/newapplication.php | 8 +- actions/oauthappssettings.php | 166 ++++++++++++++++++++++++++++++++++ actions/oauthconnectionssettings.php | 2 +- actions/showapplication.php | 25 ++---- lib/applicationeditform.php | 61 ++++++------- lib/applicationlist.php | 13 +-- lib/router.php | 23 ++--- 9 files changed, 221 insertions(+), 255 deletions(-) delete mode 100644 actions/apps.php create mode 100644 actions/oauthappssettings.php diff --git a/actions/apps.php b/actions/apps.php deleted file mode 100644 index 7c7b24570..000000000 --- a/actions/apps.php +++ /dev/null @@ -1,170 +0,0 @@ -. - * - * @category Settings - * @package StatusNet - * @author Zach Copley - * @copyright 2008-2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR . '/lib/settingsaction.php'; -require_once INSTALLDIR . '/lib/applicationlist.php'; - -/** - * Show a user's registered OAuth applications - * - * @category Settings - * @package StatusNet - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * - * @see SettingsAction - */ - -class AppsAction extends SettingsAction -{ - var $page = 0; - - function prepare($args) - { - parent::prepare($args); - $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1; - - if (!common_logged_in()) { - $this->clientError(_('You must be logged in to list your applications.')); - return false; - } - - return true; - } - - /** - * Title of the page - * - * @return string Title of the page - */ - - function title() - { - return _('OAuth applications'); - } - - /** - * Instructions for use - * - * @return instructions for use - */ - - function getInstructions() - { - return _('Applications you have registered'); - } - - /** - * Content area of the page - * - * @return void - */ - - function showContent() - { - $user = common_current_user(); - - $offset = ($this->page - 1) * APPS_PER_PAGE; - $limit = APPS_PER_PAGE + 1; - - $application = new Oauth_application(); - $application->owner = $user->id; - $application->limit($offset, $limit); - $application->orderBy('created DESC'); - $application->find(); - - $cnt = 0; - - if ($application) { - $al = new ApplicationList($application, $user, $this); - $cnt = $al->show(); - if (0 == $cnt) { - $this->showEmptyListMessage(); - } - } - - $this->elementStart('p', array('id' => 'application_register')); - $this->element('a', - array('href' => common_local_url( - 'newapplication', - array('nickname' => $user->nickname) - ), - 'class' => 'more' - ), - 'Register a new application'); - $this->elementEnd('p'); - - $this->pagination( - $this->page > 1, - $cnt > APPS_PER_PAGE, - $this->page, - 'apps', - array('nickname' => $user->nickname) - ); - } - - function showEmptyListMessage() - { - $message = sprintf(_('You have not registered any applications yet.')); - - $this->elementStart('div', 'guide'); - $this->raw(common_markup_to_html($message)); - $this->elementEnd('div'); - } - - /** - * Handle posts to this form - * - * Based on the button that was pressed, muxes out to other functions - * to do the actual task requested. - * - * All sub-functions reload the form with a message -- success or failure. - * - * @return void - */ - - function handlePost() - { - // CSRF protection - - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - - } - -} diff --git a/actions/editapplication.php b/actions/editapplication.php index a0ed3117a..a6db87c61 100644 --- a/actions/editapplication.php +++ b/actions/editapplication.php @@ -125,10 +125,7 @@ class EditApplicationAction extends OwnerDesignAction if ($this->arg('cancel')) { common_redirect(common_local_url('showapplication', - array( - 'nickname' => $cur->nickname, - 'id' => $this->app->id) - ), 303); + array('id' => $this->app->id)), 303); } elseif ($this->arg('save')) { $this->trySave(); } else { @@ -253,8 +250,7 @@ class EditApplicationAction extends OwnerDesignAction $this->app->uploadLogo(); - common_redirect(common_local_url('apps', - array('nickname' => $cur->nickname)), 303); + common_redirect(common_local_url('oauthappssettings'), 303); } } diff --git a/actions/newapplication.php b/actions/newapplication.php index 7bb81095d..c499fe7c7 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -114,8 +114,7 @@ class NewApplicationAction extends OwnerDesignAction $cur = common_current_user(); if ($this->arg('cancel')) { - common_redirect(common_local_url('apps', - array('nickname' => $cur->nickname)), 303); + common_redirect(common_local_url('oauthappssettings'), 303); } elseif ($this->arg('save')) { $this->trySave(); } else { @@ -147,7 +146,7 @@ class NewApplicationAction extends OwnerDesignAction function trySave() { - $name = $this->trimmed('name'); + $name = $this->trimmed('name'); $description = $this->trimmed('description'); $source_url = $this->trimmed('source_url'); $organization = $this->trimmed('organization'); @@ -270,8 +269,7 @@ class NewApplicationAction extends OwnerDesignAction $app->query('COMMIT'); - common_redirect(common_local_url('apps', - array('nickname' => $cur->nickname)), 303); + common_redirect(common_local_url('oauthappssettings'), 303); } diff --git a/actions/oauthappssettings.php b/actions/oauthappssettings.php new file mode 100644 index 000000000..6c0670b17 --- /dev/null +++ b/actions/oauthappssettings.php @@ -0,0 +1,166 @@ +. + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/settingsaction.php'; +require_once INSTALLDIR . '/lib/applicationlist.php'; + +/** + * Show a user's registered OAuth applications + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see SettingsAction + */ + +class OauthappssettingsAction extends SettingsAction +{ + var $page = 0; + + function prepare($args) + { + parent::prepare($args); + $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1; + + if (!common_logged_in()) { + $this->clientError(_('You must be logged in to list your applications.')); + return false; + } + + return true; + } + + /** + * Title of the page + * + * @return string Title of the page + */ + + function title() + { + return _('OAuth applications'); + } + + /** + * Instructions for use + * + * @return instructions for use + */ + + function getInstructions() + { + return _('Applications you have registered'); + } + + /** + * Content area of the page + * + * @return void + */ + + function showContent() + { + $user = common_current_user(); + + $offset = ($this->page - 1) * APPS_PER_PAGE; + $limit = APPS_PER_PAGE + 1; + + $application = new Oauth_application(); + $application->owner = $user->id; + $application->limit($offset, $limit); + $application->orderBy('created DESC'); + $application->find(); + + $cnt = 0; + + if ($application) { + $al = new ApplicationList($application, $user, $this); + $cnt = $al->show(); + if (0 == $cnt) { + $this->showEmptyListMessage(); + } + } + + $this->elementStart('p', array('id' => 'application_register')); + $this->element('a', + array('href' => common_local_url('newapplication'), + 'class' => 'more' + ), + 'Register a new application'); + $this->elementEnd('p'); + + $this->pagination( + $this->page > 1, + $cnt > APPS_PER_PAGE, + $this->page, + 'oauthappssettings' + ); + } + + function showEmptyListMessage() + { + $message = sprintf(_('You have not registered any applications yet.')); + + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); + } + + /** + * Handle posts to this form + * + * Based on the button that was pressed, muxes out to other functions + * to do the actual task requested. + * + * All sub-functions reload the form with a message -- success or failure. + * + * @return void + */ + + function handlePost() + { + // CSRF protection + + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + } + +} diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index 56e7b02fb..99bb9022b 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -158,7 +158,7 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction $this->elementStart('p'); $this->raw(_('Developers can edit the registration settings for their applications ')); $this->element('a', - array('href' => common_local_url('apps', array('nickname' => $cur->nickname))), + array('href' => common_local_url('oauthappssettings')), 'here.'); $this->elementEnd('p'); } diff --git a/actions/showapplication.php b/actions/showapplication.php index f2ff8b900..bd3337136 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -211,15 +211,9 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementStart('ul'); $this->elementStart('li', 'entity_edit'); $this->element('a', - array('href' => - common_local_url( - 'editapplication', - array( - 'nickname' => $this->owner->nickname, - 'id' => $this->application->id - ) - ) - ), 'Edit'); + array('href' => common_local_url('editapplication', + array('id' => $this->application->id))), + 'Edit'); $this->elementEnd('li'); $this->elementStart('li', 'entity_reset_keysecret'); @@ -228,8 +222,7 @@ class ShowApplicationAction extends OwnerDesignAction 'class' => 'form_reset_key', 'method' => 'POST', 'action' => common_local_url('showapplication', - array('nickname' => $cur->nickname, - 'id' => $this->application->id)))); + array('id' => $this->application->id)))); $this->elementStart('fieldset'); $this->hidden('token', common_session_token()); @@ -273,13 +266,9 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementStart('p', array('id' => 'application_action')); $this->element('a', - array( - 'href' => common_local_url( - 'apps', - array('nickname' => $this->owner->nickname)), - 'class' => 'more' - ), - 'View your applications'); + array('href' => common_local_url('oauthappssettings'), + 'class' => 'more'), + 'View your applications'); $this->elementEnd('p'); } diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index e9ab46780..040d3bf74 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -119,12 +119,9 @@ class ApplicationEditForm extends Form if (!empty($this->application)) { return common_local_url('editapplication', - array('id' => $this->application->id, - 'nickname' => $cur->nickname) - ); + array('id' => $this->application->id)); } else { - return common_local_url('newapplication', - array('nickname' => $cur->nickname)); + return common_local_url('newapplication'); } } @@ -149,7 +146,7 @@ class ApplicationEditForm extends Form { if ($this->application) { $id = $this->application->id; - $icon = $this->application->icon; + $icon = $this->application->icon; $name = $this->application->name; $description = $this->application->description; $source_url = $this->application->source_url; @@ -160,7 +157,7 @@ class ApplicationEditForm extends Form $this->access_type = $this->application->access_type; } else { $id = ''; - $icon = ''; + $icon = ''; $name = ''; $description = ''; $source_url = ''; @@ -171,26 +168,26 @@ class ApplicationEditForm extends Form $this->access_type = ''; } - $this->out->hidden('token', common_session_token()); + $this->out->hidden('token', common_session_token()); $this->out->elementStart('ul', 'form_data'); - $this->out->elementStart('li', array('id' => 'application_icon')); + $this->out->elementStart('li', array('id' => 'application_icon')); - if (!empty($icon)) { - $this->out->element('img', array('src' => $icon)); - } + if (!empty($icon)) { + $this->out->element('img', array('src' => $icon)); + } - $this->out->element('label', array('for' => 'app_icon'), - _('Icon')); + $this->out->element('label', array('for' => 'app_icon'), + _('Icon')); $this->out->element('input', array('name' => 'app_icon', - 'type' => 'file', - 'id' => 'app_icon')); + 'type' => 'file', + 'id' => 'app_icon')); $this->out->element('p', 'form_guide', _('Icon for this application')); $this->out->element('input', array('name' => 'MAX_FILE_SIZE', - 'type' => 'hidden', - 'id' => 'MAX_FILE_SIZE', - 'value' => ImageFile::maxFileSizeInt())); + 'type' => 'hidden', + 'id' => 'MAX_FILE_SIZE', + 'value' => ImageFile::maxFileSizeInt())); $this->out->elementEnd('li'); $this->out->elementStart('li'); @@ -207,13 +204,13 @@ class ApplicationEditForm extends Form $maxDesc = Oauth_application::maxDesc(); if ($maxDesc > 0) { $descInstr = sprintf(_('Describe your application in %d chars'), - $maxDesc); + $maxDesc); } else { $descInstr = _('Describe your application'); } $this->out->textarea('description', _('Description'), ($this->out->arg('description')) ? $this->out->arg('description') : $description, - $descInstr); + $descInstr); $this->out->elementEnd('li'); @@ -259,8 +256,8 @@ class ApplicationEditForm extends Form $this->out->element('input', $attrs); $this->out->element('label', array('for' => 'app_type-browser', - 'class' => 'radio'), - _('Browser')); + 'class' => 'radio'), + _('Browser')); $attrs = array('name' => 'app_type', 'type' => 'radio', @@ -275,8 +272,8 @@ class ApplicationEditForm extends Form $this->out->element('input', $attrs); $this->out->element('label', array('for' => 'app_type-desktop', - 'class' => 'radio'), - _('Desktop')); + 'class' => 'radio'), + _('Desktop')); $this->out->element('p', 'form_guide', _('Type of application, browser or desktop')); $this->out->elementEnd('li'); @@ -298,8 +295,8 @@ class ApplicationEditForm extends Form $this->out->element('input', $attrs); $this->out->element('label', array('for' => 'default_access_type-ro', - 'class' => 'radio'), - _('Read-only')); + 'class' => 'radio'), + _('Read-only')); $attrs = array('name' => 'default_access_type', 'type' => 'radio', @@ -309,15 +306,15 @@ class ApplicationEditForm extends Form if ($this->application->access_type & Oauth_application::$readAccess && $this->application->access_type & Oauth_application::$writeAccess - ) { + ) { $attrs['checked'] = 'checked'; } $this->out->element('input', $attrs); $this->out->element('label', array('for' => 'default_access_type-rw', - 'class' => 'radio'), - _('Read-write')); + 'class' => 'radio'), + _('Read-write')); $this->out->element('p', 'form_guide', _('Default access for this application: read-only, or read-write')); $this->out->elementEnd('li'); @@ -334,8 +331,8 @@ class ApplicationEditForm extends Form function formActions() { $this->out->submit('cancel', _('Cancel'), 'submit form_action-primary', - 'cancel', _('Cancel')); + 'cancel', _('Cancel')); $this->out->submit('save', _('Save'), 'submit form_action-secondary', - 'save', _('Save')); + 'save', _('Save')); } } diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 15c2d588a..f2eaefb40 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -64,7 +64,7 @@ class ApplicationList extends Widget $this->application = $application; $this->owner = $owner; $this->action = $action; - $this->connections = $connections; + $this->connections = $connections; } function show() @@ -97,10 +97,9 @@ class ApplicationList extends Widget $this->out->elementStart('span', 'vcard author'); if (!$this->connections) { $this->out->elementStart('a', - array('href' => common_local_url('showapplication', - array('nickname' => $user->nickname, - 'id' => $this->application->id)), - 'class' => 'url')); + array('href' => common_local_url('showapplication', + array('id' => $this->application->id)), + 'class' => 'url')); } else { $this->out->elementStart('a', array('href' => $this->application->source_url, @@ -154,8 +153,4 @@ class ApplicationList extends Widget return; } - function highlight($text) - { - return htmlspecialchars($text); - } } diff --git a/lib/router.php b/lib/router.php index d6e448c2f..42bff2778 100644 --- a/lib/router.php +++ b/lib/router.php @@ -141,7 +141,7 @@ class Router // settings foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections', - 'email', 'sms', 'userdesign', 'other') as $s) { + 'oauthapps', 'email', 'sms', 'userdesign', 'other') as $s) { $m->connect('settings/'.$s, array('action' => $s.'settings')); } @@ -634,28 +634,23 @@ class Router // user stuff foreach (array('subscriptions', 'subscribers', - 'nudge', 'all', 'foaf', 'xrds', 'apps', + 'nudge', 'all', 'foaf', 'xrds', 'replies', 'inbox', 'outbox', 'microsummary') as $a) { $m->connect(':nickname/'.$a, array('action' => $a), array('nickname' => '[a-zA-Z0-9]{1,64}')); } - $m->connect(':nickname/apps', - array('action' => 'apps'), - array('nickname' => '['.NICKNAME_FMT.']{1,64}')); - $m->connect(':nickname/apps/show/:id', + $m->connect('settings/oauthapps/show/:id', array('action' => 'showapplication'), - array('nickname' => '['.NICKNAME_FMT.']{1,64}', - 'id' => '[0-9]+') + array('id' => '[0-9]+') ); - $m->connect(':nickname/apps/new', - array('action' => 'newapplication'), - array('nickname' => '['.NICKNAME_FMT.']{1,64}')); - $m->connect(':nickname/apps/edit/:id', + $m->connect('settings/oauthapps/new', + array('action' => 'newapplication') + ); + $m->connect('settings/oauthapps/edit/:id', array('action' => 'editapplication'), - array('nickname' => '['.NICKNAME_FMT.']{1,64}', - 'id' => '[0-9]+') + array('id' => '[0-9]+') ); $m->connect('api/oauth/request_token', -- cgit v1.2.3-54-g00ecf From c0eee277d1058c9c291b3c4474cc8a72cb8c6d0e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 11:31:15 +0000 Subject: Make sure applications are really looked up by consumer key --- actions/apioauthauthorize.php | 42 +++--------------------------------------- lib/apioauthstore.php | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index cdf9cb7df..0966ba1d7 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -74,42 +74,11 @@ class ApiOauthAuthorizeAction extends ApiOauthAction $this->oauth_token = $this->arg('oauth_token'); $this->callback = $this->arg('oauth_callback'); $this->store = new ApiStatusNetOAuthDataStore(); + $this->app = $this->store->getAppByRequestToken($this->oauth_token); return true; } - function getApp() - { - // Look up the full req token - - $req_token = $this->store->lookup_token(null, - 'request', - $this->oauth_token); - - if (empty($req_token)) { - - common_debug("Couldn't find request token!"); - - $this->clientError(_('Bad request.')); - return; - } - - // Look up the app - - $app = new Oauth_application(); - $app->consumer_key = $req_token->consumer_key; - $result = $app->find(true); - - if (!empty($result)) { - $this->app = $app; - return true; - - } else { - common_debug("couldn't find the app!"); - return false; - } - } - /** * Handle input, produce output * @@ -140,7 +109,8 @@ class ApiOauthAuthorizeAction extends ApiOauthAction return; } - if (!$this->getApp()) { + if (empty($this->app)) { + common_debug('No app for that token.'); $this->clientError(_('Bad request.')); return; } @@ -166,11 +136,6 @@ class ApiOauthAuthorizeAction extends ApiOauthAction return; } - if (!$this->getApp()) { - $this->clientError(_('Bad request.')); - return; - } - // check creds $user = null; @@ -416,7 +381,6 @@ class ApiOauthAuthorizeAction extends ApiOauthAction function getInstructions() { return _('Allow or deny access to your account information.'); - } /** diff --git a/lib/apioauthstore.php b/lib/apioauthstore.php index c39ddbb0f..32110d057 100644 --- a/lib/apioauthstore.php +++ b/lib/apioauthstore.php @@ -36,6 +36,44 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore $con->consumer_secret); } + function getAppByRequestToken($token_key) + { + // Look up the full req tokenx + + $req_token = $this->lookup_token(null, + 'request', + $token_key); + + if (empty($req_token)) { + common_debug("couldn't get request token from oauth datastore"); + return null; + } + + // Look up the full Token + + $token = new Token(); + $token->tok = $req_token->key; + $result = $token->find(true); + + if (empty($result)) { + common_debug('Couldn\'t find req token in the token table.'); + return null; + } + + // Look up the app + + $app = new Oauth_application(); + $app->consumer_key = $token->consumer_key; + $result = $app->find(true); + + if (!empty($result)) { + return $app; + } else { + common_debug("Couldn't find the app!"); + return null; + } + } + function new_access_token($token, $consumer) { common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__); @@ -64,7 +102,7 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore if (!empty($result)) { common_debug("Oath app user found."); } else { - common_debug("Oauth app user not found."); + common_debug("Oauth app user not found. app id $app->id token $rt->tok"); return null; } -- cgit v1.2.3-54-g00ecf From ba68e042a8acb9dd1054e0bc1c5cd4dfd415642e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 17:52:25 +0000 Subject: Fix user count --- actions/showapplication.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/actions/showapplication.php b/actions/showapplication.php index bd3337136..b21b994aa 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -194,10 +194,13 @@ class ShowApplicationAction extends OwnerDesignAction $defaultAccess = ($this->application->access_type & Oauth_application::$writeAccess) ? 'read-write' : 'read-only'; $profile = Profile::staticGet($this->application->owner); - $userCnt = 0; // XXX: count how many users use the app + + $appUsers = new Oauth_application_user(); + $appUsers->application_id = $this->application->id; + $userCnt = $appUsers->count(); $this->raw(sprintf( - _('Created by %1$s - %2$s access by default - %3$d users.'), + _('created by %1$s - %2$s access by default - %3$d users'), $profile->getBestName(), $defaultAccess, $userCnt @@ -222,7 +225,7 @@ class ShowApplicationAction extends OwnerDesignAction 'class' => 'form_reset_key', 'method' => 'POST', 'action' => common_local_url('showapplication', - array('id' => $this->application->id)))); + array('id' => $this->application->id)))); $this->elementStart('fieldset'); $this->hidden('token', common_session_token()); -- cgit v1.2.3-54-g00ecf From 7b3c099f953c1569c18d81fdb8d4230e927d429e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 18:20:03 +0000 Subject: Ensure only the application's owner can edit it --- actions/editapplication.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/actions/editapplication.php b/actions/editapplication.php index a6db87c61..9cc3e3cea 100644 --- a/actions/editapplication.php +++ b/actions/editapplication.php @@ -45,9 +45,9 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { class EditApplicationAction extends OwnerDesignAction { - var $msg = null; - - var $app = null; + var $msg = null; + var $owner = null; + var $app = null; function title() { @@ -68,7 +68,14 @@ class EditApplicationAction extends OwnerDesignAction } $id = (int)$this->arg('id'); - $this->app = Oauth_application::staticGet($id); + + $this->app = Oauth_application::staticGet($id); + $this->owner = User::staticGet($this->app->owner); + $cur = common_current_user(); + + if ($cur->id != $this->owner->id) { + $this->clientError(_('You are not the owner of this application.'), 401); + } if (!$this->app) { $this->clientError(_('No such application.')); -- cgit v1.2.3-54-g00ecf From cff2cfd7a7566542ce860410e7ef006a84869c7e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 18:33:13 +0000 Subject: Fix approval date and label on apps list --- lib/applicationlist.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index f2eaefb40..6eae26135 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -136,8 +136,8 @@ class ApplicationList extends Widget $access = ($this->application->access_type & Oauth_application::$writeAccess) ? 'read-write' : 'read-only'; - $txt = 'Approved ' . common_exact_date($appUser->modified) . - " $access for access."; + $txt = 'Approved ' . common_date_string($appUser->modified) . + " - $access access."; $this->out->raw($txt); $this->out->elementEnd('li'); -- cgit v1.2.3-54-g00ecf From 6d58ef4abb12d735b6be777ea79f99a07c68694a Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 13 Jan 2010 20:10:09 +0000 Subject: Updated apioauthauthorize markup and styles --- actions/apioauthauthorize.php | 46 +++++++++++-------------------------------- theme/base/css/display.css | 10 ++++++++-- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index 0966ba1d7..72d142651 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -273,27 +273,6 @@ class ApiOauthAuthorizeAction extends ApiOauthAction return _('An application would like to connect to your account'); } - /** - * Show page notice - * - * Display a notice for how to use the page, or the - * error if it exists. - * - * @return void - */ - - function showPageNotice() - { - if ($this->error) { - $this->element('p', 'error', $this->error); - } else { - $instr = $this->getInstructions(); - $output = common_markup_to_html($instr); - - $this->raw($output); - } - } - /** * Shows the authorization form. * @@ -303,40 +282,38 @@ class ApiOauthAuthorizeAction extends ApiOauthAction function showContent() { $this->elementStart('form', array('method' => 'post', - 'id' => 'form_login', + 'id' => 'form_apioauthauthorize', 'class' => 'form_settings', 'action' => common_local_url('apioauthauthorize'))); + $this->elementStart('fieldset'); + $this->element('legend', array('id' => 'apioauthauthorize_allowdeny'), + _('Allow or deny access')); $this->hidden('token', common_session_token()); $this->hidden('oauth_token', $this->oauth_token); $this->hidden('oauth_callback', $this->callback); - $this->elementStart('fieldset'); - - $this->elementStart('ul'); + $this->elementStart('ul', 'form_data'); $this->elementStart('li'); + $this->elementStart('p'); if (!empty($this->app->icon)) { $this->element('img', array('src' => $this->app->icon)); } - $this->elementEnd('li'); - $this->elementStart('li'); $access = ($this->app->access_type & Oauth_application::$writeAccess) ? 'access and update' : 'access'; - $msg = _("The application %s by %s would like " . - "the ability to %s your account data."); + $msg = _("The application %s by %s would like " . + "the ability to %s your account data."); $this->raw(sprintf($msg, $this->app->name, $this->app->organization, $access)); - + $this->elementEnd('p'); $this->elementEnd('li'); $this->elementEnd('ul'); - $this->elementEnd('fieldset'); - if (!common_logged_in()) { $this->elementStart('fieldset'); @@ -355,17 +332,18 @@ class ApiOauthAuthorizeAction extends ApiOauthAction } $this->element('input', array('id' => 'deny_submit', - 'class' => 'submit', + 'class' => 'submit submit form_action-primary', 'name' => 'deny', 'type' => 'submit', 'value' => _('Deny'))); $this->element('input', array('id' => 'allow_submit', - 'class' => 'submit', + 'class' => 'submit submit form_action-secondary', 'name' => 'allow', 'type' => 'submit', 'value' => _('Allow'))); + $this->elementEnd('fieldset'); $this->elementEnd('form'); } diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 82670c964..ff81d3727 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -177,7 +177,8 @@ font-weight:bold; #form_password_recover legend, #form_password_change legend, .form_entity_block legend, -#form_filter_bytag legend { +#form_filter_bytag legend, +#apioauthauthorize_allowdeny { display:none; } @@ -906,10 +907,15 @@ list-style-type:none; } .application img, #showapplication .entity_profile img, -#editapplication .form_data #application_icon img { +#editapplication .form_data #application_icon, +#apioauthauthorize .form_data img { max-width:96px; max-height:96px; } +#apioauthauthorize .form_data img { +margin-right:18px; +float:left; +} #showapplication .entity_profile { width:68%; } -- cgit v1.2.3-54-g00ecf From dbcbc2fe7ff0368910a49ed5675b0edc1e2bf18d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 13 Jan 2010 20:43:23 +0000 Subject: Changed legend text from Login to Account because it is not really logging iny --- actions/apioauthauthorize.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index 72d142651..fa074c4e7 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -317,7 +317,7 @@ class ApiOauthAuthorizeAction extends ApiOauthAction if (!common_logged_in()) { $this->elementStart('fieldset'); - $this->element('legend', null, _('Login')); + $this->element('legend', null, _('Account')); $this->elementStart('ul', 'form_data'); $this->elementStart('li'); $this->input('nickname', _('Nickname')); -- cgit v1.2.3-54-g00ecf From 9e7f47652d860d7f1e296dd369c4e68814bf2636 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 21:11:08 +0000 Subject: Revoke access token UI --- actions/oauthconnectionssettings.php | 62 +++++++++++++++++++++++++++++++----- actions/showapplication.php | 1 + classes/Oauth_application_user.php | 2 +- classes/Profile.php | 3 +- lib/applicationlist.php | 14 +++++++- 5 files changed, 71 insertions(+), 11 deletions(-) diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index 99bb9022b..b17729b82 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -50,10 +50,12 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction { var $page = null; + var $id = null; function prepare($args) { parent::prepare($args); + $this->id = (int)$this->arg('id'); $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1; return true; } @@ -101,16 +103,16 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction $application = $profile->getApplications($offset, $limit); - $cnt == 0; + $cnt == 0; - if (!empty($application)) { - $al = new ApplicationList($application, $user, $this, true); - $cnt = $al->show(); - } + if (!empty($application)) { + $al = new ApplicationList($application, $user, $this, true); + $cnt = $al->show(); + } - if ($cnt == 0) { - $this->showEmptyListMessage(); - } + if ($cnt == 0) { + $this->showEmptyListMessage(); + } $this->pagination($this->page > 1, $cnt > APPS_PER_PAGE, $this->page, 'connectionssettings', @@ -139,6 +141,50 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction return; } + if ($this->arg('revoke')) { + $this->revokeAccess($this->id); + + // XXX: Show some indicator to the user of what's been done. + + $this->showPage(); + } else { + $this->clientError(_('Unexpected form submission.'), 401); + return false; + } + } + + function revokeAccess($appId) + { + $cur = common_current_user(); + + $app = Oauth_application::staticGet('id', $appId); + + if (empty($app)) { + $this->clientError(_('No such application.'), 404); + return false; + } + + $appUser = Oauth_application_user::getByKeys($cur, $app); + + if (empty($appUser)) { + $this->clientError(_('You are not a user of that application.'), 401); + return false; + } + + $orig = clone($appUser); + $appUser->access_type = 0; // No access + $result = $appUser->update(); + + if (!$result) { + common_log_db_error($orig, 'UPDATE', __FILE__); + $this->clientError(_('Unable to revoke access for app: ' . $app->id)); + return false; + } + + $msg = 'User %s (id: %d) revoked access to app %s (id: %d)'; + common_log(LOG_INFO, sprintf($msg, $cur->nickname, + $cur->id, $app->name, $app->id)); + } function showEmptyListMessage() diff --git a/actions/showapplication.php b/actions/showapplication.php index b21b994aa..049206375 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -92,6 +92,7 @@ class ShowApplicationAction extends OwnerDesignAction if ($cur->id != $this->owner->id) { $this->clientError(_('You are not the owner of this application.'), 401); + return false; } return true; diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php index a05371f56..618d68133 100644 --- a/classes/Oauth_application_user.php +++ b/classes/Oauth_application_user.php @@ -34,7 +34,7 @@ class Oauth_application_user extends Memcached_DataObject $oau = new Oauth_application_user(); $oau->profile_id = $user->id; - $oau->application_id = $app->id; + $oau->application_id = $app->id; $oau->limit(1); $result = $oau->find(true); diff --git a/classes/Profile.php b/classes/Profile.php index fef2a2171..1076fb2cb 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -358,7 +358,8 @@ class Profile extends Memcached_DataObject 'SELECT a.* ' . 'FROM oauth_application_user u, oauth_application a ' . 'WHERE u.profile_id = %d ' . - 'AND a.id = u.application_id ' . + 'AND a.id = u.application_id ' . + 'AND u.access_type > 0 ' . 'ORDER BY u.created DESC '; if ($offset > 0) { diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 6eae26135..3abb1f8aa 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -142,7 +142,19 @@ class ApplicationList extends Widget $this->out->raw($txt); $this->out->elementEnd('li'); - // XXX: Add revoke access button + $this->out->elementStart('li', 'entity_revoke'); + $this->out->elementStart('form', array('id' => 'form_revoke_app', + 'class' => 'form_revoke_app', + 'method' => 'POST', + 'action' => + common_local_url('oauthconnectionssettings'))); + $this->out->elementStart('fieldset'); + $this->out->hidden('id', $this->application->id); + $this->out->hidden('token', common_session_token()); + $this->out->submit('revoke', _('Revoke')); + $this->out->elementEnd('fieldset'); + $this->out->elementEnd('form'); + $this->out->elementEnd('li'); } } -- cgit v1.2.3-54-g00ecf From d33040089d660d897183df8e94c2a65bb8c8c85f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 21:14:22 +0000 Subject: Remove verifier from Oauth_application_user (not needed there) --- classes/Oauth_application_user.php | 1 - classes/statusnet.ini | 1 - db/statusnet.sql | 1 - 3 files changed, 3 deletions(-) diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php index 618d68133..57986281f 100644 --- a/classes/Oauth_application_user.php +++ b/classes/Oauth_application_user.php @@ -14,7 +14,6 @@ class Oauth_application_user extends Memcached_DataObject public $application_id; // int(4) primary_key not_null public $access_type; // tinyint(1) public $token; // varchar(255) - public $verifier; // varchar(255) public $created; // datetime not_null public $modified; // timestamp not_null default_CURRENT_TIMESTAMP diff --git a/classes/statusnet.ini b/classes/statusnet.ini index b358bbf60..2b5f2c225 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -373,7 +373,6 @@ profile_id = 129 application_id = 129 access_type = 17 token = 2 -verifier = 2 created = 142 modified = 384 diff --git a/db/statusnet.sql b/db/statusnet.sql index b1867e7f0..3a74f9d7a 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -230,7 +230,6 @@ create table oauth_application_user ( application_id integer not null comment 'id of the application' references oauth_application (id), access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked', token varchar(255) comment 'request or access token', - verifier varchar(255) not null comment 'verification code', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified', constraint primary key (profile_id, application_id) -- cgit v1.2.3-54-g00ecf From 6efbf2777ac1ba934829a8e9ae381ca280621c0c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 21:31:19 +0000 Subject: Add verifier and verified callback to token for OAuth 1.0a --- classes/Token.php | 6 ++++-- classes/statusnet.ini | 2 ++ db/statusnet.sql | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/classes/Token.php b/classes/Token.php index 1fabd72f1..a129d1fd1 100644 --- a/classes/Token.php +++ b/classes/Token.php @@ -4,7 +4,7 @@ */ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Token extends Memcached_DataObject +class Token extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -14,7 +14,9 @@ class Token extends Memcached_DataObject public $tok; // char(32) primary_key not_null public $secret; // char(32) not_null public $type; // tinyint(1) not_null - public $state; // tinyint(1) + public $state; // tinyint(1) + public $verifier; // varchar(255) + public $verified_callback; // varchar(255) public $created; // datetime() not_null public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 2b5f2c225..6203650a6 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -516,6 +516,8 @@ tok = 130 secret = 130 type = 145 state = 17 +verifier = 2 +verified_callback = 2 created = 142 modified = 384 diff --git a/db/statusnet.sql b/db/statusnet.sql index 3a74f9d7a..17de4fd0d 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -189,6 +189,8 @@ create table token ( secret char(32) not null comment 'secret value', type tinyint not null default 0 comment 'request or access', state tinyint default 0 comment 'for requests, 0 = initial, 1 = authorized, 2 = used', + verifier varchar(255) comment 'verifier string for OAuth 1.0a', + verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified', -- cgit v1.2.3-54-g00ecf From de70b91a3a42b07c86d3a0cd8868ded6510fd91c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 16:52:33 -0800 Subject: Some rough test scripts for poking at the OAuth system --- tests/oauth/README | 22 +++++++++ tests/oauth/exchangetokens.php | 105 ++++++++++++++++++++++++++++++++++++++++ tests/oauth/getrequesttoken.php | 71 +++++++++++++++++++++++++++ tests/oauth/oauth.ini | 10 ++++ tests/oauth/verifycreds.php | 101 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 309 insertions(+) create mode 100644 tests/oauth/README create mode 100755 tests/oauth/exchangetokens.php create mode 100755 tests/oauth/getrequesttoken.php create mode 100644 tests/oauth/oauth.ini create mode 100755 tests/oauth/verifycreds.php diff --git a/tests/oauth/README b/tests/oauth/README new file mode 100644 index 000000000..ea4aabadb --- /dev/null +++ b/tests/oauth/README @@ -0,0 +1,22 @@ +Some very rough test scripts for hitting up the OAuth endpoints. + +Note: this works best if you register an OAuth application, leaving +the callback URL blank. + +Put your instance info and consumer key and secret in oauth.ini + +Example usage: +-------------- + +php getrequesttoken.php + +Gets and request token, token secret and a url to authorize it. Once +you get the token/secret you can exchange it for an access token... + +php exchangetokens.php --oauth_token=b9a79548a88c1aa9a5bea73103c6d41d --token_secret=4a47d9337fc0202a14ab552e17a3b657 + +Once you have your access token, go ahead and try an protected API +resource: + +php verifycreds.php --oauth_token=cf2de7665f0dda0a82c2dc39b01be7f9 --token_secret=4524c3b712200138e1a4cff2e9ca83d8 + diff --git a/tests/oauth/exchangetokens.php b/tests/oauth/exchangetokens.php new file mode 100755 index 000000000..2394826c7 --- /dev/null +++ b/tests/oauth/exchangetokens.php @@ -0,0 +1,105 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); + +require_once INSTALLDIR . '/extlib/OAuth.php'; + +$ini = parse_ini_file("oauth.ini"); + +$test_consumer = new OAuthConsumer($ini['consumer_key'], $ini['consumer_secret']); + +$at_endpoint = $ini['apiroot'] . $ini['access_token_url']; + +$shortoptions = 't:s:'; +$longoptions = array('oauth_token=', 'token_secret='); + +$helptext = <<sign_request($hmac_method, $test_consumer, $rt); + +$r = httpRequest($req_req->to_url()); + +common_debug("Exchange request token = " . var_export($rt, true)); +common_debug("Exchange tokens URL: " . $req_req->to_url()); + +$body = $r->getBody(); + +$token_stuff = array(); +parse_str($body, $token_stuff); + +print 'Access token : ' . $token_stuff['oauth_token'] . "\n"; +print 'Access token secret : ' . $token_stuff['oauth_token_secret'] . "\n"; + +function httpRequest($url) +{ + $request = HTTPClient::start(); + + $request->setConfig(array( + 'follow_redirects' => true, + 'connect_timeout' => 120, + 'timeout' => 120, + 'ssl_verify_peer' => false, + 'ssl_verify_host' => false + )); + + return $request->get($url); +} + diff --git a/tests/oauth/getrequesttoken.php b/tests/oauth/getrequesttoken.php new file mode 100755 index 000000000..fc546a0f4 --- /dev/null +++ b/tests/oauth/getrequesttoken.php @@ -0,0 +1,71 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); + +require_once INSTALLDIR . '/scripts/commandline.inc'; +require_once INSTALLDIR . '/extlib/OAuth.php'; + +$ini = parse_ini_file("oauth.ini"); + +$test_consumer = new OAuthConsumer($ini['consumer_key'], $ini['consumer_secret']); + +$rt_endpoint = $ini['apiroot'] . $ini['request_token_url']; + +$parsed = parse_url($rt_endpoint); +$params = array(); + +parse_str($parsed['query'], $params); + +$hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + +$req_req = OAuthRequest::from_consumer_and_token($test_consumer, NULL, "GET", $rt_endpoint, $params); +$req_req->sign_request($hmac_method, $test_consumer, NULL); + +$r = httpRequest($req_req->to_url()); + +$body = $r->getBody(); + +$token_stuff = array(); +parse_str($body, $token_stuff); + +$authurl = $ini['apiroot'] . $ini['authorize_url'] . '?oauth_token=' . $token_stuff['oauth_token']; + +print 'Request token : ' . $token_stuff['oauth_token'] . "\n"; +print 'Request token secret : ' . $token_stuff['oauth_token_secret'] . "\n"; +print "Authorize URL : $authurl\n"; + +//var_dump($req_req); + +function httpRequest($url) +{ + $request = HTTPClient::start(); + + $request->setConfig(array( + 'follow_redirects' => true, + 'connect_timeout' => 120, + 'timeout' => 120, + 'ssl_verify_peer' => false, + 'ssl_verify_host' => false + )); + + return $request->get($url); +} + diff --git a/tests/oauth/oauth.ini b/tests/oauth/oauth.ini new file mode 100644 index 000000000..5ef0e571e --- /dev/null +++ b/tests/oauth/oauth.ini @@ -0,0 +1,10 @@ +; Setup OAuth info here +apiroot = "http://dev.controlyourself.ca/zach/api" + +request_token_url = "/oauth/request_token" +authorize_url = "/oauth/authorize" +access_token_url = "/oauth/access_token" + +consumer_key = "b748968e9bea81a53f3a3c15aa0c686f" +consumer_secret = "5434e18cce05d9e53cdd48029a62fa41" + diff --git a/tests/oauth/verifycreds.php b/tests/oauth/verifycreds.php new file mode 100755 index 000000000..873bdb8bd --- /dev/null +++ b/tests/oauth/verifycreds.php @@ -0,0 +1,101 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); + +require_once INSTALLDIR . '/extlib/OAuth.php'; + +$shortoptions = 'o:s:'; +$longoptions = array('oauth_token=', 'token_secret='); + +$helptext = <<sign_request($hmac_method, $test_consumer, $at); + +$r = httpRequest($req_req->to_url()); + +$body = $r->getBody(); + +print "$body\n"; + +//print $req_req->to_url() . "\n\n"; + +function httpRequest($url) +{ + $request = HTTPClient::start(); + + $request->setConfig(array( + 'follow_redirects' => true, + 'connect_timeout' => 120, + 'timeout' => 120, + 'ssl_verify_peer' => false, + 'ssl_verify_host' => false + )); + + return $request->get($url); +} + -- cgit v1.2.3-54-g00ecf From c2c930a8556cca866dfa0eba2fe1a8242eef71f2 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 16:56:52 -0800 Subject: Fixed some spelling mistakes in the README --- tests/oauth/README | 6 +++--- tests/oauth/oauth.ini | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/oauth/README b/tests/oauth/README index ea4aabadb..dd76feb0c 100644 --- a/tests/oauth/README +++ b/tests/oauth/README @@ -10,12 +10,12 @@ Example usage: php getrequesttoken.php -Gets and request token, token secret and a url to authorize it. Once -you get the token/secret you can exchange it for an access token... +Gets a request token, token secret and a url to authorize it. Once +you authorize the request token you can exchange it for an access token... php exchangetokens.php --oauth_token=b9a79548a88c1aa9a5bea73103c6d41d --token_secret=4a47d9337fc0202a14ab552e17a3b657 -Once you have your access token, go ahead and try an protected API +Once you have your access token, go ahead and try a protected API resource: php verifycreds.php --oauth_token=cf2de7665f0dda0a82c2dc39b01be7f9 --token_secret=4524c3b712200138e1a4cff2e9ca83d8 diff --git a/tests/oauth/oauth.ini b/tests/oauth/oauth.ini index 5ef0e571e..16b747fe4 100644 --- a/tests/oauth/oauth.ini +++ b/tests/oauth/oauth.ini @@ -1,5 +1,5 @@ ; Setup OAuth info here -apiroot = "http://dev.controlyourself.ca/zach/api" +apiroot = "http://YOURSTATUSNET/api" request_token_url = "/oauth/request_token" authorize_url = "/oauth/authorize" -- cgit v1.2.3-54-g00ecf From 1f8ddf716d0b54cc40aa89e595fe2232a10e7a2a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 14 Jan 2010 02:16:03 +0000 Subject: Check for read vs. read-write access on OAuth authenticated API mehtods. --- lib/api.php | 5 +++++ lib/apiauth.php | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/api.php b/lib/api.php index 707e4ac21..794b14050 100644 --- a/lib/api.php +++ b/lib/api.php @@ -53,6 +53,9 @@ if (!defined('STATUSNET')) { class ApiAction extends Action { + const READ_ONLY = 1; + const READ_WRITE = 2; + var $format = null; var $user = null; var $auth_user = null; @@ -62,6 +65,8 @@ class ApiAction extends Action var $since_id = null; var $since = null; + var $access = self::READ_ONLY; // read (default) or read-write + /** * Initialization. * diff --git a/lib/apiauth.php b/lib/apiauth.php index 431f3ac4f..8374c24a7 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -78,12 +78,27 @@ class ApiAuthAction extends ApiAction $this->checkOAuthRequest(); } else { $this->checkBasicAuthUser(); + // By default, all basic auth users have read and write access + + $this->access = self::READ_WRITE; } } return true; } + function handle($args) + { + parent::handle($args); + + if ($this->isReadOnly($args) == false) { + if ($this->access == self::READ_ONLY) { + $this->clientError(_('API method requires write access.'), 401); + exit(); + } + } + } + function checkOAuthRequest() { common_debug("We have an OAuth request."); @@ -130,6 +145,10 @@ class ApiAuthAction extends ApiAction if ($this->oauth_access_type != 0) { + // Set the read or read-write access for the api call + $this->access = ($appUser->access_type & Oauth_application::$writeAccess) + ? self::READ_WRITE : self::READ_ONLY; + $this->auth_user = User::staticGet('id', $appUser->profile_id); $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . @@ -220,6 +239,7 @@ class ApiAuthAction extends ApiAction exit; } } + return true; } -- cgit v1.2.3-54-g00ecf From 6a4c88afe723b246cf3a2158ac2c1ad4161bdd43 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 14 Jan 2010 02:32:59 +0000 Subject: More relaxed selector for application icon and form checkbox --- theme/base/css/display.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index ff81d3727..84e9426c7 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -907,7 +907,7 @@ list-style-type:none; } .application img, #showapplication .entity_profile img, -#editapplication .form_data #application_icon, +.form_data #application_icon img, #apioauthauthorize .form_data img { max-width:96px; max-height:96px; @@ -944,9 +944,9 @@ margin-left:1.795%; font-family:monospace; font-size:1.3em; } -#editapplication .form_data #application_types label.radio, -#editapplication .form_data #default_access_types label.radio { -width:15%; +.form_data #application_types label.radio, +.form_data #default_access_types label.radio { +width:14.5%; } /* NOTICE */ -- cgit v1.2.3-54-g00ecf From 8b24b5ac7bea2098d3c85e342526c2102e2a6fb9 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 20 Jan 2010 18:01:07 -0800 Subject: Add Start/EndSetApiUser events when setting API user via OAuth --- lib/apiauth.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/apiauth.php b/lib/apiauth.php index 8374c24a7..f513ed2c9 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -149,7 +149,10 @@ class ApiAuthAction extends ApiAction $this->access = ($appUser->access_type & Oauth_application::$writeAccess) ? self::READ_WRITE : self::READ_ONLY; - $this->auth_user = User::staticGet('id', $appUser->profile_id); + if (Event::handle('StartSetApiUser', array(&$user))) { + $this->auth_user = User::staticGet('id', $appUser->profile_id); + Event::handle('EndSetApiUser', array($user)); + } $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . "application '%s' (id: %d)."; -- cgit v1.2.3-54-g00ecf From 4daf76212a6802863d20c6af7597eddded227ae8 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 14 Jan 2010 02:38:01 +0000 Subject: - Had to remove checking read vs. read-write in OAuth authenticated methods - Will now pick up source attr from OAuth app --- actions/apiaccountverifycredentials.php | 14 ++++++++++++++ actions/apistatusesupdate.php | 5 +++++ lib/apiauth.php | 14 +++++--------- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/actions/apiaccountverifycredentials.php b/actions/apiaccountverifycredentials.php index 08b201dbf..1095d5162 100644 --- a/actions/apiaccountverifycredentials.php +++ b/actions/apiaccountverifycredentials.php @@ -82,4 +82,18 @@ class ApiAccountVerifyCredentialsAction extends ApiAuthAction } + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + * + **/ + + function isReadOnly($args) + { + return true; + } + } diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index f594bbf39..f8bf7cf87 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -85,6 +85,11 @@ class ApiStatusesUpdateAction extends ApiAuthAction $this->lat = $this->trimmed('lat'); $this->lon = $this->trimmed('long'); + // try to set the source attr from OAuth app + if (empty($this->source)) { + $this->source = $this->oauth_source; + } + if (empty($this->source) || in_array($this->source, self::$reserved_sources)) { $this->source = 'api'; } diff --git a/lib/apiauth.php b/lib/apiauth.php index f513ed2c9..37070d212 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -55,6 +55,7 @@ class ApiAuthAction extends ApiAction { var $access_token; var $oauth_access_type; + var $oauth_source; /** * Take arguments for running, and output basic auth header if needed @@ -90,13 +91,6 @@ class ApiAuthAction extends ApiAction function handle($args) { parent::handle($args); - - if ($this->isReadOnly($args) == false) { - if ($this->access == self::READ_ONLY) { - $this->clientError(_('API method requires write access.'), 401); - exit(); - } - } } function checkOAuthRequest() @@ -116,8 +110,6 @@ class ApiAuthAction extends ApiAction $req = OAuthRequest::from_request(); $server->verify_request($req); - common_debug("Good OAuth request!"); - $app = Oauth_application::getByConsumerKey($this->consumer_key); if (empty($app)) { @@ -129,6 +121,10 @@ class ApiAuthAction extends ApiAction throw new OAuthException('No application for that consumer key.'); } + // set the source attr + + $this->oauth_source = $app->name; + $appUser = Oauth_application_user::staticGet('token', $this->access_token); -- cgit v1.2.3-54-g00ecf From e6cf293db8c465b21899fd328152a991501a5038 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:39:32 -0500 Subject: Recover caching logic lost in bad merge I made a bad merge on Jan 10th from master to 0.9.x. This lost a number of memcache enhancements made on the 0.9.x branch. I've been able to re-do the manual merge, and this represents the changes. Most of them are related to caching on insert. --- classes/Memcached_DataObject.php | 144 ++++++++++++++++++++++++++------------- 1 file changed, 98 insertions(+), 46 deletions(-) diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 33645a3e8..f59213c2c 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -66,7 +66,6 @@ class Memcached_DataObject extends DB_DataObject // Clear this out so we don't accidentally break global // state in *this* process. $this->_DB_resultid = null; - // We don't have any local DBO refs, so clear these out. $this->_link_loaded = false; } @@ -91,9 +90,7 @@ class Memcached_DataObject extends DB_DataObject unset($i); } $i = Memcached_DataObject::getcached($cls, $k, $v); - if ($i) { - return $i; - } else { + if ($i === false) { // false == cache miss $i = DB_DataObject::factory($cls); if (empty($i)) { $i = false; @@ -101,22 +98,34 @@ class Memcached_DataObject extends DB_DataObject } $result = $i->get($k, $v); if ($result) { + // Hit! $i->encache(); - return $i; } else { + // save the fact that no such row exists + $c = self::memcache(); + if (!empty($c)) { + $ck = self::cachekey($cls, $k, $v); + $c->set($ck, null); + } $i = false; - return $i; } } + return $i; } - function &pkeyGet($cls, $kv) + /** + * @fixme Should this return false on lookup fail to match staticGet? + */ + function pkeyGet($cls, $kv) { $i = Memcached_DataObject::multicache($cls, $kv); - if ($i) { + if ($i !== false) { // false == cache miss return $i; } else { - $i = new $cls(); + $i = DB_DataObject::factory($cls); + if (empty($i)) { + return false; + } foreach ($kv as $k => $v) { $i->$k = $v; } @@ -124,6 +133,11 @@ class Memcached_DataObject extends DB_DataObject $i->encache(); } else { $i = null; + $c = self::memcache(); + if (!empty($c)) { + $ck = self::multicacheKey($cls, $kv); + $c->set($ck, null); + } } return $i; } @@ -132,6 +146,9 @@ class Memcached_DataObject extends DB_DataObject function insert() { $result = parent::insert(); + if ($result) { + $this->encache(); // in case of cached negative lookups + } return $result; } @@ -186,6 +203,17 @@ class Memcached_DataObject extends DB_DataObject function keyTypes() { + // ini-based classes return number-indexed arrays. handbuilt + // classes return column => keytype. Make this uniform. + + $keys = $this->keys(); + + $keyskeys = array_keys($keys); + + if (is_string($keyskeys[0])) { + return $keys; + } + global $_DB_DATAOBJECT; if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) { $this->databaseStructure(); @@ -197,6 +225,7 @@ class Memcached_DataObject extends DB_DataObject function encache() { $c = $this->memcache(); + if (!$c) { return false; } else if ($this->tableName() == 'user' && is_object($this->id)) { @@ -206,64 +235,86 @@ class Memcached_DataObject extends DB_DataObject str_replace("\n", " ", $e->getTraceAsString())); return false; } else { - $pkey = array(); - $pval = array(); - $types = $this->keyTypes(); - ksort($types); - foreach ($types as $key => $type) { - if ($type == 'K') { - $pkey[] = $key; - $pval[] = $this->$key; - } else { - $c->set($this->cacheKey($this->tableName(), $key, $this->$key), $this); - } - } - # XXX: should work for both compound and scalar pkeys - $pvals = implode(',', $pval); - $pkeys = implode(',', $pkey); - $c->set($this->cacheKey($this->tableName(), $pkeys, $pvals), $this); + $keys = $this->_allCacheKeys(); + + foreach ($keys as $key) { + $c->set($key, $this); + } } } function decache() { $c = $this->memcache(); + if (!$c) { return false; - } else { - $pkey = array(); - $pval = array(); - $types = $this->keyTypes(); - ksort($types); - foreach ($types as $key => $type) { - if ($type == 'K') { - $pkey[] = $key; - $pval[] = $this->$key; - } else { - $c->delete($this->cacheKey($this->tableName(), $key, $this->$key)); + } + + $keys = $this->_allCacheKeys(); + + foreach ($keys as $key) { + $c->delete($key, $this); + } + } + + function _allCacheKeys() + { + $ckeys = array(); + + $types = $this->keyTypes(); + ksort($types); + + $pkey = array(); + $pval = array(); + + foreach ($types as $key => $type) { + + assert(!empty($key)); + + if ($type == 'U') { + if (empty($this->$key)) { + continue; } + $ckeys[] = $this->cacheKey($this->tableName(), $key, $this->$key); + } else if ($type == 'K' || $type == 'N') { + $pkey[] = $key; + $pval[] = $this->$key; + } else { + throw new Exception("Unknown key type $key => $type for " . $this->tableName()); } - # should work for both compound and scalar pkeys - # XXX: comma works for now but may not be safe separator for future keys - $pvals = implode(',', $pval); - $pkeys = implode(',', $pkey); - $c->delete($this->cacheKey($this->tableName(), $pkeys, $pvals)); } + + assert(count($pkey) > 0); + + // XXX: should work for both compound and scalar pkeys + $pvals = implode(',', $pval); + $pkeys = implode(',', $pkey); + + $ckeys[] = $this->cacheKey($this->tableName(), $pkeys, $pvals); + + return $ckeys; } function multicache($cls, $kv) { ksort($kv); - $c = Memcached_DataObject::memcache(); + $c = self::memcache(); if (!$c) { return false; } else { - $pkeys = implode(',', array_keys($kv)); - $pvals = implode(',', array_values($kv)); - return $c->get(Memcached_DataObject::cacheKey($cls, $pkeys, $pvals)); + return $c->get(self::multicacheKey($cls, $kv)); } } + static function multicacheKey($cls, $kv) + { + ksort($kv); + $pkeys = implode(',', array_keys($kv)); + $pvals = implode(',', array_values($kv)); + return self::cacheKey($cls, $pkeys, $pvals); + } + function getSearchEngine($table) { require_once INSTALLDIR.'/lib/search_engines.php'; @@ -298,7 +349,8 @@ class Memcached_DataObject extends DB_DataObject $key_part = common_keyize($cls).':'.md5($qry); $ckey = common_cache_key($key_part); $stored = $c->get($ckey); - if ($stored) { + + if ($stored !== false) { return new ArrayWrapper($stored); } -- cgit v1.2.3-54-g00ecf From e400437d5783a5a1a03feb3ea3e5b6a40a71ed60 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:50:07 -0500 Subject: fix interpolation for positional arguments in showstream --- actions/showstream.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/showstream.php b/actions/showstream.php index 75e10858d..90ff67073 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -76,7 +76,7 @@ class ShowstreamAction extends ProfileAction if ($this->page == 1) { return $base; } else { - return sprintf(_("%1$s, page %2$d"), + return sprintf(_('%1$s, page %2$d'), $base, $this->page); } -- cgit v1.2.3-54-g00ecf From fc7afed92478117d009d835a4d1cd0565ada1089 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:52:03 -0500 Subject: fix interpolation for positional arguments in replies --- actions/replies.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/replies.php b/actions/replies.php index 2e50f1c3c..164c328db 100644 --- a/actions/replies.php +++ b/actions/replies.php @@ -124,7 +124,7 @@ class RepliesAction extends OwnerDesignAction if ($this->page == 1) { return sprintf(_("Replies to %s"), $this->user->nickname); } else { - return sprintf(_("Replies to %1$s, page %2$d"), + return sprintf(_('Replies to %1$s, page %2$d'), $this->user->nickname, $this->page); } -- cgit v1.2.3-54-g00ecf From 02526f1100621aa522ccb6beccc0a4e507da328a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:53:29 -0500 Subject: fix interpolation of positional arguments to sprintf in outbox --- actions/outbox.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/outbox.php b/actions/outbox.php index de30de018..b81d4b9d0 100644 --- a/actions/outbox.php +++ b/actions/outbox.php @@ -55,10 +55,10 @@ class OutboxAction extends MailboxAction function title() { if ($this->page > 1) { - return sprintf(_("Outbox for %1$s - page %2$d"), + return sprintf(_('Outbox for %1$s - page %2$d'), $this->user->nickname, $page); } else { - return sprintf(_("Outbox for %s"), $this->user->nickname); + return sprintf(_('Outbox for %s'), $this->user->nickname); } } -- cgit v1.2.3-54-g00ecf From 9077db00a50a6123ece70eb690b658c19c5aeb27 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:54:25 -0500 Subject: fix interpolation of positional arguments to sprintf in inbox --- actions/inbox.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/inbox.php b/actions/inbox.php index f605cc9e8..8330f753f 100644 --- a/actions/inbox.php +++ b/actions/inbox.php @@ -56,10 +56,10 @@ class InboxAction extends MailboxAction function title() { if ($this->page > 1) { - return sprintf(_("Inbox for %1$s - page %2$d"), $this->user->nickname, + return sprintf(_('Inbox for %1$s - page %2$d'), $this->user->nickname, $this->page); } else { - return sprintf(_("Inbox for %s"), $this->user->nickname); + return sprintf(_('Inbox for %s'), $this->user->nickname); } } -- cgit v1.2.3-54-g00ecf From 73fdec6c12c308732f0673f2d10ab1542c927a33 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:55:29 -0500 Subject: fix interpolation of positional arguments to sprintf in usergroups --- actions/usergroups.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/usergroups.php b/actions/usergroups.php index 504226143..97faabae6 100644 --- a/actions/usergroups.php +++ b/actions/usergroups.php @@ -59,9 +59,9 @@ class UsergroupsAction extends OwnerDesignAction function title() { if ($this->page == 1) { - return sprintf(_("%s groups"), $this->user->nickname); + return sprintf(_('%s groups'), $this->user->nickname); } else { - return sprintf(_("%1$s groups, page %2$d"), + return sprintf(_('%1$s groups, page %2$d'), $this->user->nickname, $this->page); } -- cgit v1.2.3-54-g00ecf From 019dad95e12191632a3828fb0950a80f305bbf01 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:56:41 -0500 Subject: fix interpolation of positional arguments to sprintf in show favorites --- actions/showfavorites.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/showfavorites.php b/actions/showfavorites.php index 6023f0156..f2d082293 100644 --- a/actions/showfavorites.php +++ b/actions/showfavorites.php @@ -74,9 +74,9 @@ class ShowfavoritesAction extends OwnerDesignAction function title() { if ($this->page == 1) { - return sprintf(_("%s's favorite notices"), $this->user->nickname); + return sprintf(_('%s\'s favorite notices'), $this->user->nickname); } else { - return sprintf(_("%1$s's favorite notices, page %2$d"), + return sprintf(_('%1$s\'s favorite notices, page %2$d'), $this->user->nickname, $this->page); } -- cgit v1.2.3-54-g00ecf From a9da43a41644b3e1ff27e8250fd858d2c2840e7b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:57:33 -0500 Subject: fix interpolation of positional arguments to sprintf in show group --- actions/showgroup.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/showgroup.php b/actions/showgroup.php index 06ae572e8..8042a4951 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -79,9 +79,9 @@ class ShowgroupAction extends GroupDesignAction } if ($this->page == 1) { - return sprintf(_("%s group"), $base); + return sprintf(_('%s group'), $base); } else { - return sprintf(_("%1$s group, page %2$d"), + return sprintf(_('%1$s group, page %2$d'), $base, $this->page); } -- cgit v1.2.3-54-g00ecf From 089305ac7a89ba42286973feb14ca23687ec0995 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:59:22 -0500 Subject: fix interpolation of positional arguments to sprintf in tag action --- actions/tag.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actions/tag.php b/actions/tag.php index 12857236e..e91df6ea9 100644 --- a/actions/tag.php +++ b/actions/tag.php @@ -63,9 +63,9 @@ class TagAction extends Action function title() { if ($this->page == 1) { - return sprintf(_("Notices tagged with %s"), $this->tag); + return sprintf(_('Notices tagged with %s'), $this->tag); } else { - return sprintf(_("Notices tagged with %1$s, page %2$d"), + return sprintf(_('Notices tagged with %1$s, page %2$d'), $this->tag, $this->page); } @@ -85,7 +85,7 @@ class TagAction extends Action array('tag' => $this->tag)), sprintf(_('Notice feed for tag %s (RSS 1.0)'), $this->tag)), - new Feed(Feed::RSS2, + new Feed(Feed::RSS2, common_local_url('ApiTimelineTag', array('format' => 'rss', 'tag' => $this->tag)), -- cgit v1.2.3-54-g00ecf From 73f6250b9d8a1f65bfcbf76b05f087bd6e118bea Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 25 Jan 2010 14:55:04 +0000 Subject: An update to geolocation cookie to use a single file and set the expiry date to 30 days from now. --- js/util.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/js/util.js b/js/util.js index a7339010a..8d52d859b 100644 --- a/js/util.js +++ b/js/util.js @@ -495,7 +495,7 @@ var SN = { // StatusNet $('#'+SN.C.S.NoticeLocationId).val(''); $('#'+SN.C.S.NoticeDataGeo).attr('checked', false); - $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled'); + $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled', { path: '/', expires: SN.U.GetDateFromNow(30) }); } function getJSONgeocodeURL(geocodeURL, data) { @@ -537,7 +537,8 @@ var SN = { // StatusNet NLNU: location.url, NDG: true }; - $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue)); + + $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue), { path: '/', expires: SN.U.GetDateFromNow(30) }); }); } @@ -658,6 +659,13 @@ var SN = { // StatusNet } return false; }); + }, + + GetDateFromNow: function(days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + + return date; } }, -- cgit v1.2.3-54-g00ecf From c10d5320dd5b308795c5a5bda2441fabfe278a84 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 25 Jan 2010 09:07:24 -0800 Subject: Disable PubSubHubBub hub pings automatically on private site (hub wouldn't be able to read feeds anyway) [Might be good to think of a core way to mark a plugin as disabled when it initializes.] --- plugins/PubSubHubBub/PubSubHubBubPlugin.php | 44 +++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/plugins/PubSubHubBub/PubSubHubBubPlugin.php b/plugins/PubSubHubBub/PubSubHubBubPlugin.php index ce6086df9..a880dc866 100644 --- a/plugins/PubSubHubBub/PubSubHubBubPlugin.php +++ b/plugins/PubSubHubBub/PubSubHubBubPlugin.php @@ -79,6 +79,21 @@ class PubSubHubBubPlugin extends Plugin parent::__construct(); } + /** + * Check if plugin should be active; may be mass-enabled. + * @return boolean + */ + + function enabled() + { + if (common_config('site', 'private')) { + // PuSH relies on public feeds + return false; + } + // @fixme check for being on a private network? + return true; + } + /** * Hooks the StartApiAtom event * @@ -92,8 +107,9 @@ class PubSubHubBubPlugin extends Plugin function onStartApiAtom($action) { - $action->element('link', array('rel' => 'hub', 'href' => $this->hub), null); - + if ($this->enabled()) { + $action->element('link', array('rel' => 'hub', 'href' => $this->hub), null); + } return true; } @@ -110,9 +126,11 @@ class PubSubHubBubPlugin extends Plugin function onStartApiRss($action) { - $action->element('atom:link', array('rel' => 'hub', - 'href' => $this->hub), - null); + if ($this->enabled()) { + $action->element('atom:link', array('rel' => 'hub', + 'href' => $this->hub), + null); + } return true; } @@ -130,6 +148,9 @@ class PubSubHubBubPlugin extends Plugin function onHandleQueuedNotice($notice) { + if (!$this->enabled()) { + return false; + } $publisher = new Publisher($this->hub); $feeds = array(); @@ -243,16 +264,21 @@ class PubSubHubBubPlugin extends Plugin function onPluginVersion(&$versions) { + $about = _m('The PubSubHubBub plugin pushes RSS/Atom updates '. + 'to a PubSubHubBub hub.'); + if (!$this->enabled()) { + $about = '' . $about . ' ' . + _m('(inactive on private site)'); + } $versions[] = array('name' => 'PubSubHubBub', 'version' => STATUSNET_VERSION, 'author' => 'Craig Andrews', 'homepage' => 'http://status.net/wiki/Plugin:PubSubHubBub', 'rawdescription' => - _m('The PubSubHubBub plugin pushes RSS/Atom updates '. - 'to a PubSubHubBub hub.')); + $about); return true; } -- cgit v1.2.3-54-g00ecf From 055a00bcaeaa50c36143bd151bf4433c5c87d174 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 25 Jan 2010 09:36:20 -0800 Subject: drop now-unused --skip-xmpp and --xmpp-only options from queuedaemon.php --- scripts/queuedaemon.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/scripts/queuedaemon.php b/scripts/queuedaemon.php index a9cfda6d7..bedd14b1a 100755 --- a/scripts/queuedaemon.php +++ b/scripts/queuedaemon.php @@ -21,7 +21,7 @@ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); $shortoptions = 'fi:at:'; -$longoptions = array('id=', 'foreground', 'all', 'threads=', 'skip-xmpp', 'xmpp-only'); +$longoptions = array('id=', 'foreground', 'all', 'threads='); /** * Attempts to get a count of the processors available on the current system @@ -163,13 +163,6 @@ if (!$threads) { $daemonize = !(have_option('f') || have_option('--foreground')); $all = have_option('a') || have_option('--all'); -if (have_option('--skip-xmpp')) { - define('XMPP_EMERGENCY_FLAG', true); -} -if (have_option('--xmpp-only')) { - define('XMPP_ONLY_FLAG', true); -} - $daemon = new QueueDaemon($id, $daemonize, $threads, $all); $daemon->runOnce(); -- cgit v1.2.3-54-g00ecf From ee4ca8f2601bc5a2dd258fea176037e03e23feb8 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 25 Jan 2010 09:41:40 -0800 Subject: quick fix to console.php: don't save blank lines into readline history --- scripts/console.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/console.php b/scripts/console.php index 8b62a3a96..4d207c261 100755 --- a/scripts/console.php +++ b/scripts/console.php @@ -45,10 +45,12 @@ function read_input_line($prompt) if (CONSOLE_INTERACTIVE) { if (CONSOLE_READLINE) { $line = readline($prompt); - readline_add_history($line); - if (defined('CONSOLE_HISTORY')) { - // Save often; it's easy to hit fatal errors. - readline_write_history(CONSOLE_HISTORY); + if (trim($line) != '') { + readline_add_history($line); + if (defined('CONSOLE_HISTORY')) { + // Save often; it's easy to hit fatal errors. + readline_write_history(CONSOLE_HISTORY); + } } return $line; } else { -- cgit v1.2.3-54-g00ecf From 1ab24832960eef698731cf0d3e72fccfa336392d Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 25 Jan 2010 13:48:24 -0800 Subject: Fix presence notification on XMPP thread (now foreground, not background) --- lib/xmppmanager.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php index 299175dd7..985e7c32e 100644 --- a/lib/xmppmanager.php +++ b/lib/xmppmanager.php @@ -101,7 +101,7 @@ class XmppManager extends IoManager $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this); $this->conn->setReconnectTimeout(600); - jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', -1); + jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', 100); return !is_null($this->conn); } @@ -233,7 +233,7 @@ class XmppManager extends IoManager common_log(LOG_NOTICE, 'XMPP reconnected'); $this->conn->processUntil('session_start'); - $this->conn->presence(null, 'available', null, 'available', -1); + $this->conn->presence(null, 'available', null, 'available', 100); } -- cgit v1.2.3-54-g00ecf From d17b7fa19ba2bfec9aa3d734c0f30e001256ddc0 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 26 Jan 2010 01:58:10 +0100 Subject: Setting the geo location cookie expire date far into the future: 2029 ;) --- js/util.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/util.js b/js/util.js index 8d52d859b..b864867fd 100644 --- a/js/util.js +++ b/js/util.js @@ -495,7 +495,7 @@ var SN = { // StatusNet $('#'+SN.C.S.NoticeLocationId).val(''); $('#'+SN.C.S.NoticeDataGeo).attr('checked', false); - $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled', { path: '/', expires: SN.U.GetDateFromNow(30) }); + $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled', { path: '/', expires: SN.U.GetFullYear(2029, 0, 1) }); } function getJSONgeocodeURL(geocodeURL, data) { @@ -538,7 +538,7 @@ var SN = { // StatusNet NDG: true }; - $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue), { path: '/', expires: SN.U.GetDateFromNow(30) }); + $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue), { path: '/', expires: SN.U.GetFullYear(2029, 0, 1) }); }); } @@ -661,9 +661,9 @@ var SN = { // StatusNet }); }, - GetDateFromNow: function(days) { + GetFullYear: function(year, month, day) { var date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + date.setFullYear(year, month, day); return date; } -- cgit v1.2.3-54-g00ecf From 655573c213113861cfd82a42ef999dde1aeea149 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 26 Jan 2010 00:21:05 -0500 Subject: Single-user mode New configuration options to define a single-user mode. This hides most of the "community" pages, like the public timeline and groups. The main user's timeline becomes the main page, and most other URLs are changed. Switching back and forth between 1-user and multi-user mode is probably hazardous. Squashed commit of the following: commit d814aa5c92d14a27a12baba7893f3f8bf63f1d08 Author: Evan Prodromou Date: Tue Jan 26 00:17:27 2010 -0500 don't show inbox and outbox in single-user mode commit 47f19b9523a7015d4c6e460b73ea32c839e00aa1 Author: Evan Prodromou Date: Tue Jan 26 00:15:22 2010 -0500 show correct URL for logo in single-user mode commit 552010cffc33eadbc512ec5a67619dbc2015239a Author: Evan Prodromou Date: Tue Jan 26 00:15:06 2010 -0500 make singleuser its own config section commit 786ab260a3ca172e57b555c75ca10946d8f258a1 Author: Evan Prodromou Date: Tue Jan 26 00:05:19 2010 -0500 make single-user mode work commit 5b21d7309b3a8dd5a4e0f29aea76f7897f1818b1 Author: Evan Prodromou Date: Mon Jan 25 23:45:55 2010 -0500 add single-user mode --- README | 9 +++ lib/action.php | 8 +- lib/default.php | 5 +- lib/personalgroupnav.php | 7 +- lib/router.php | 207 ++++++++++++++++++++++++++++++----------------- 5 files changed, 157 insertions(+), 79 deletions(-) diff --git a/README b/README index 602288789..f83873ca8 100644 --- a/README +++ b/README @@ -1492,6 +1492,15 @@ disabled: whether to enable this command. If enabled, users who send should enable it only after you've convinced yourself that it is safe. Default is 'false'. +singleuser +---------- + +If an installation has only one user, this can simplify a lot of the +interface. It also makes the user's profile the root URL. + +enabled: Whether to run in "single user mode". Default false. +nickname: nickname of the single user. + Plugins ======= diff --git a/lib/action.php b/lib/action.php index e24277558..3ffc452de 100644 --- a/lib/action.php +++ b/lib/action.php @@ -392,8 +392,14 @@ class Action extends HTMLOutputter // lawsuit $this->elementStart('address', array('id' => 'site_contact', 'class' => 'vcard')); if (Event::handle('StartAddressData', array($this))) { + if (common_config('singleuser', 'enabled')) { + $url = common_local_url('showstream', + array('nickname' => common_config('singleuser', 'nickname'))); + } else { + $url = common_local_url('public'); + } $this->elementStart('a', array('class' => 'url home bookmark', - 'href' => common_local_url('public'))); + 'href' => $url)); if (common_config('site', 'logo') || file_exists(Theme::file('logo.png'))) { $this->element('img', array('class' => 'logo photo', 'src' => (common_config('site', 'logo')) ? common_config('site', 'logo') : Theme::path('logo.png'), diff --git a/lib/default.php b/lib/default.php index b6ee72279..35115542f 100644 --- a/lib/default.php +++ b/lib/default.php @@ -56,7 +56,7 @@ $default = 'dupelimit' => 60, # default for same person saying the same thing 'textlimit' => 140, 'indent' => true, - 'use_x_sendfile' => false, + 'use_x_sendfile' => false ), 'db' => array('database' => 'YOU HAVE TO SET THIS IN config.php', @@ -260,4 +260,7 @@ $default = ), 'admin' => array('panels' => array('design', 'site', 'user', 'paths')), + 'singleuser' => + array('enabled' => false, + 'nickname' => null), ); diff --git a/lib/personalgroupnav.php b/lib/personalgroupnav.php index cdde1feca..25db5baa9 100644 --- a/lib/personalgroupnav.php +++ b/lib/personalgroupnav.php @@ -78,9 +78,9 @@ class PersonalGroupNav extends Widget function show() { $user = null; - + // FIXME: we should probably pass this in - + $action = $this->action->trimmed('action'); $nickname = $this->action->trimmed('nickname'); @@ -117,7 +117,8 @@ class PersonalGroupNav extends Widget $cur = common_current_user(); - if ($cur && $cur->id == $user->id) { + if ($cur && $cur->id == $user->id && + !common_config('singleuser', 'enabled')) { $this->out->menuItem(common_local_url('inbox', array('nickname' => $nickname)), diff --git a/lib/router.php b/lib/router.php index 42bff2778..c0ddc8db3 100644 --- a/lib/router.php +++ b/lib/router.php @@ -73,12 +73,6 @@ class Router if (Event::handle('StartInitializeRouter', array(&$m))) { - // In the "root" - - $m->connect('', array('action' => 'public')); - $m->connect('rss', array('action' => 'publicrss')); - $m->connect('featuredrss', array('action' => 'featuredrss')); - $m->connect('favoritedrss', array('action' => 'favoritedrss')); $m->connect('opensearch/people', array('action' => 'opensearch', 'type' => 'people')); $m->connect('opensearch/notice', array('action' => 'opensearch', @@ -145,6 +139,18 @@ class Router $m->connect('settings/'.$s, array('action' => $s.'settings')); } + $m->connect('settings/oauthapps/show/:id', + array('action' => 'showapplication'), + array('id' => '[0-9]+') + ); + $m->connect('settings/oauthapps/new', + array('action' => 'newapplication') + ); + $m->connect('settings/oauthapps/edit/:id', + array('action' => 'editapplication'), + array('id' => '[0-9]+') + ); + // search foreach (array('group', 'people', 'notice') as $s) { @@ -227,11 +233,6 @@ class Router array('action' => 'peopletag'), array('tag' => '[a-zA-Z0-9]+')); - $m->connect('featured/', array('action' => 'featured')); - $m->connect('featured', array('action' => 'featured')); - $m->connect('favorited/', array('action' => 'favorited')); - $m->connect('favorited', array('action' => 'favorited')); - // groups $m->connect('group/new', array('action' => 'newgroup')); @@ -622,6 +623,17 @@ class Router $m->connect('api/search.json', array('action' => 'twitapisearchjson')); $m->connect('api/trends.json', array('action' => 'twitapitrends')); + $m->connect('api/oauth/request_token', + array('action' => 'apioauthrequesttoken')); + + $m->connect('api/oauth/access_token', + array('action' => 'apioauthaccesstoken')); + + $m->connect('api/oauth/authorize', + array('action' => 'apioauthauthorize')); + + // Admin + $m->connect('admin/site', array('action' => 'siteadminpanel')); $m->connect('admin/design', array('action' => 'designadminpanel')); $m->connect('admin/user', array('action' => 'useradminpanel')); @@ -631,78 +643,125 @@ class Router array('action' => 'getfile'), array('filename' => '[A-Za-z0-9._-]+')); - // user stuff + // In the "root" - foreach (array('subscriptions', 'subscribers', - 'nudge', 'all', 'foaf', 'xrds', - 'replies', 'inbox', 'outbox', 'microsummary') as $a) { - $m->connect(':nickname/'.$a, - array('action' => $a), + if (common_config('singleuser', 'enabled')) { + + $nickname = common_config('singleuser', 'nickname'); + + foreach (array('subscriptions', 'subscribers', + 'all', 'foaf', 'xrds', + 'replies', 'microsummary') as $a) { + $m->connect($a, + array('action' => $a, + 'nickname' => $nickname)); + } + + foreach (array('subscriptions', 'subscribers') as $a) { + $m->connect($a.'/:tag', + array('action' => $a, + 'nickname' => $nickname), + array('tag' => '[a-zA-Z0-9]+')); + } + + foreach (array('rss', 'groups') as $a) { + $m->connect($a, + array('action' => 'user'.$a, + 'nickname' => $nickname)); + } + + foreach (array('all', 'replies', 'favorites') as $a) { + $m->connect($a.'/rss', + array('action' => $a.'rss', + 'nickname' => $nickname)); + } + + $m->connect('favorites', + array('action' => 'showfavorites', + 'nickname' => $nickname)); + + $m->connect('avatar/:size', + array('action' => 'avatarbynickname', + 'nickname' => $nickname), + array('size' => '(original|96|48|24)')); + + $m->connect('tag/:tag/rss', + array('action' => 'userrss', + 'nickname' => $nickname), + array('tag' => '[a-zA-Z0-9]+')); + + $m->connect('tag/:tag', + array('action' => 'showstream', + 'nickname' => $nickname), + array('tag' => '[a-zA-Z0-9]+')); + + $m->connect('', + array('action' => 'showstream', + 'nickname' => $nickname)); + + } else { + + $m->connect('', array('action' => 'public')); + $m->connect('rss', array('action' => 'publicrss')); + $m->connect('featuredrss', array('action' => 'featuredrss')); + $m->connect('favoritedrss', array('action' => 'favoritedrss')); + $m->connect('featured/', array('action' => 'featured')); + $m->connect('featured', array('action' => 'featured')); + $m->connect('favorited/', array('action' => 'favorited')); + $m->connect('favorited', array('action' => 'favorited')); + + foreach (array('subscriptions', 'subscribers', + 'nudge', 'all', 'foaf', 'xrds', + 'replies', 'inbox', 'outbox', 'microsummary') as $a) { + $m->connect(':nickname/'.$a, + array('action' => $a), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + } + + foreach (array('subscriptions', 'subscribers') as $a) { + $m->connect(':nickname/'.$a.'/:tag', + array('action' => $a), + array('tag' => '[a-zA-Z0-9]+', + 'nickname' => '[a-zA-Z0-9]{1,64}')); + } + + foreach (array('rss', 'groups') as $a) { + $m->connect(':nickname/'.$a, + array('action' => 'user'.$a), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + } + + foreach (array('all', 'replies', 'favorites') as $a) { + $m->connect(':nickname/'.$a.'/rss', + array('action' => $a.'rss'), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + } + + $m->connect(':nickname/favorites', + array('action' => 'showfavorites'), array('nickname' => '[a-zA-Z0-9]{1,64}')); - } - - $m->connect('settings/oauthapps/show/:id', - array('action' => 'showapplication'), - array('id' => '[0-9]+') - ); - $m->connect('settings/oauthapps/new', - array('action' => 'newapplication') - ); - $m->connect('settings/oauthapps/edit/:id', - array('action' => 'editapplication'), - array('id' => '[0-9]+') - ); - - $m->connect('api/oauth/request_token', - array('action' => 'apioauthrequesttoken')); - - $m->connect('api/oauth/access_token', - array('action' => 'apioauthaccesstoken')); - - $m->connect('api/oauth/authorize', - array('action' => 'apioauthauthorize')); - foreach (array('subscriptions', 'subscribers') as $a) { - $m->connect(':nickname/'.$a.'/:tag', - array('action' => $a), - array('tag' => '[a-zA-Z0-9]+', + $m->connect(':nickname/avatar/:size', + array('action' => 'avatarbynickname'), + array('size' => '(original|96|48|24)', 'nickname' => '[a-zA-Z0-9]{1,64}')); - } - foreach (array('rss', 'groups') as $a) { - $m->connect(':nickname/'.$a, - array('action' => 'user'.$a), - array('nickname' => '[a-zA-Z0-9]{1,64}')); - } + $m->connect(':nickname/tag/:tag/rss', + array('action' => 'userrss'), + array('nickname' => '[a-zA-Z0-9]{1,64}'), + array('tag' => '[a-zA-Z0-9]+')); + + $m->connect(':nickname/tag/:tag', + array('action' => 'showstream'), + array('nickname' => '[a-zA-Z0-9]{1,64}'), + array('tag' => '[a-zA-Z0-9]+')); - foreach (array('all', 'replies', 'favorites') as $a) { - $m->connect(':nickname/'.$a.'/rss', - array('action' => $a.'rss'), + $m->connect(':nickname', + array('action' => 'showstream'), array('nickname' => '[a-zA-Z0-9]{1,64}')); } - $m->connect(':nickname/favorites', - array('action' => 'showfavorites'), - array('nickname' => '[a-zA-Z0-9]{1,64}')); - - $m->connect(':nickname/avatar/:size', - array('action' => 'avatarbynickname'), - array('size' => '(original|96|48|24)', - 'nickname' => '[a-zA-Z0-9]{1,64}')); - - $m->connect(':nickname/tag/:tag/rss', - array('action' => 'userrss'), - array('nickname' => '[a-zA-Z0-9]{1,64}'), - array('tag' => '[a-zA-Z0-9]+')); - - $m->connect(':nickname/tag/:tag', - array('action' => 'showstream'), - array('nickname' => '[a-zA-Z0-9]{1,64}'), - array('tag' => '[a-zA-Z0-9]+')); - - $m->connect(':nickname', - array('action' => 'showstream'), - array('nickname' => '[a-zA-Z0-9]{1,64}')); + // user stuff Event::handle('RouterInitialized', array($m)); } -- cgit v1.2.3-54-g00ecf From 7fc5588c5dfc0a74cbc1c791bc445e399207c443 Mon Sep 17 00:00:00 2001 From: Julien C Date: Tue, 8 Dec 2009 22:16:03 +0100 Subject: Allow logging in using Twitter Signed-off-by: Julien C --- plugins/TwitterBridge/TwitterBridgePlugin.php | 26 ++ plugins/TwitterBridge/twitter_connect.gif | Bin 0 -> 2205 bytes plugins/TwitterBridge/twitterauthorization.php | 433 +++++++++++++++++++++++-- plugins/TwitterBridge/twitterlogin.php | 95 ++++++ 4 files changed, 527 insertions(+), 27 deletions(-) create mode 100644 plugins/TwitterBridge/twitter_connect.gif create mode 100644 plugins/TwitterBridge/twitterlogin.php diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index 57b3c1c99..e39ec7be0 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -72,9 +72,34 @@ class TwitterBridgePlugin extends Plugin $m->connect('twitter/authorization', array('action' => 'twitterauthorization')); $m->connect('settings/twitter', array('action' => 'twittersettings')); + + $m->connect('main/twitterlogin', array('action' => 'twitterlogin')); return true; } + + + + /* + * Add a login tab for Twitter Connect + * + * @param Action &action the current action + * + * @return void + */ + function onEndLoginGroupNav(&$action) + { + + $action_name = $action->trimmed('action'); + + $action->menuItem(common_local_url('twitterlogin'), + _('Twitter'), + _('Login or register using Twitter'), + 'twitterlogin' === $action_name); + + return true; + } + /** * Add the Twitter Settings page to the Connect Settings menu @@ -108,6 +133,7 @@ class TwitterBridgePlugin extends Plugin switch ($cls) { case 'TwittersettingsAction': case 'TwitterauthorizationAction': + case 'TwitterloginAction': include_once INSTALLDIR . '/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; diff --git a/plugins/TwitterBridge/twitter_connect.gif b/plugins/TwitterBridge/twitter_connect.gif new file mode 100644 index 000000000..e3d8f3ed7 Binary files /dev/null and b/plugins/TwitterBridge/twitter_connect.gif differ diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index 4af2f0394..8ae725374 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -21,7 +21,7 @@ * * @category TwitterauthorizationAction * @package StatusNet - * @author Zach Copely + * @author Zach Copley * @copyright 2009 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ @@ -50,6 +50,10 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; */ class TwitterauthorizationAction extends Action { + var $twuid = null; + var $tw_fields = null; + var $access_token = null; + /** * Initialize class members. Looks for 'oauth_token' parameter. * @@ -76,29 +80,56 @@ class TwitterauthorizationAction extends Action function handle($args) { parent::handle($args); - - if (!common_logged_in()) { - $this->clientError(_m('Not logged in.'), 403); + + if (common_logged_in()) { + $user = common_current_user(); + $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); + + // If there's already a foreign link record, it means we already + // have an access token, and this is unecessary. So go back. + + if (isset($flink)) { + common_redirect(common_local_url('twittersettings')); + } } - - $user = common_current_user(); - $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); - - // If there's already a foreign link record, it means we already - // have an access token, and this is unecessary. So go back. - - if (isset($flink)) { - common_redirect(common_local_url('twittersettings')); - } - - // $this->oauth_token is only populated once Twitter authorizes our - // request token. If it's empty we're at the beginning of the auth - // process - - if (empty($this->oauth_token)) { - $this->authorizeRequestToken(); + + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + // User was not logged in to StatusNet before + $this->twuid = $this->trimmed('twuid'); + $this->tw_fields = array("name" => $this->trimmed('name'), "fullname" => $this->trimmed('fullname')); + $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret')); + + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. Try again, please.')); + return; + } + if ($this->arg('create')) { + if (!$this->boolean('license')) { + $this->showForm(_('You can\'t register if you don\'t agree to the license.'), + $this->trimmed('newname')); + return; + } + $this->createNewUser(); + } else if ($this->arg('connect')) { + $this->connectNewUser(); + } else { + common_debug('Twitter Connect Plugin - ' . + print_r($this->args, true)); + $this->showForm(_('Something weird happened.'), + $this->trimmed('newname')); + } } else { - $this->saveAccessToken(); + // $this->oauth_token is only populated once Twitter authorizes our + // request token. If it's empty we're at the beginning of the auth + // process + + if (empty($this->oauth_token)) { + $this->authorizeRequestToken(); + } else { + $this->saveAccessToken(); + } } } @@ -170,16 +201,25 @@ class TwitterauthorizationAction extends Action $this->serverError(_m('Couldn\'t link your Twitter account.')); } - // Save the access token and Twitter user info - - $this->saveForeignLink($atok, $twitter_user); + if (common_logged_in()) { + // Save the access token and Twitter user info + $this->saveForeignLink($atok, $twitter_user); + } + else{ + $this->twuid = $twitter_user->id; + $this->tw_fields = array("name" => $twitter_user->screen_name, "fullname" => $twitter_user->name); + $this->access_token = $atok; + $this->tryLogin(); + } // Clean up the the mess we made in the session unset($_SESSION['twitter_request_token']); unset($_SESSION['twitter_request_token_secret']); - - common_redirect(common_local_url('twittersettings')); + + if (common_logged_in()) { + common_redirect(common_local_url('twittersettings')); + } } /** @@ -220,5 +260,344 @@ class TwitterauthorizationAction extends Action save_twitter_user($twitter_user->id, $twitter_user->screen_name); } + + + + function showPageNotice() + { + if ($this->error) { + $this->element('div', array('class' => 'error'), $this->error); + } else { + $this->element('div', 'instructions', + sprintf(_('This is the first time you\'ve logged into %s so we must connect your Twitter account to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name'))); + } + } + + function title() + { + return _('Twitter Account Setup'); + } + + function showForm($error=null, $username=null) + { + $this->error = $error; + $this->username = $username; + + $this->showPage(); + } + + function showPage() + { + parent::showPage(); + } + + function showContent() + { + if (!empty($this->message_text)) { + $this->element('p', null, $this->message); + return; + } + + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_twitter_connect', + 'class' => 'form_settings', + 'action' => common_local_url('twitterauthorization'))); + $this->elementStart('fieldset', array('id' => 'settings_twitter_connect_options')); + $this->element('legend', null, _('Connection options')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->element('input', array('type' => 'checkbox', + 'id' => 'license', + 'class' => 'checkbox', + 'name' => 'license', + 'value' => 'true')); + $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license')); + $this->text(_('My text and files are available under ')); + $this->element('a', array('href' => common_config('license', 'url')), + common_config('license', 'title')); + $this->text(_(' except this private data: password, email address, IM address, phone number.')); + $this->elementEnd('label'); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->hidden('access_token_key', $this->access_token->key); + $this->hidden('access_token_secret', $this->access_token->secret); + $this->hidden('twuid', $this->twuid); + $this->hidden('tw_fields_name', $this->tw_fields['name']); + $this->hidden('tw_fields_fullname', $this->tw_fields['fullname']); + + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->element('legend', null, + _('Create new account')); + $this->element('p', null, + _('Create a new user with this nickname.')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('newname', _('New nickname'), + ($this->username) ? $this->username : '', + _('1-64 lowercase letters or numbers, no punctuation or spaces')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('create', _('Create')); + $this->elementEnd('fieldset'); + + $this->elementStart('fieldset'); + $this->element('legend', null, + _('Connect existing account')); + $this->element('p', null, + _('If you already have an account, login with your username and password to connect it to your Twitter account.')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('nickname', _('Existing nickname')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->password('password', _('Password')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('connect', _('Connect')); + $this->elementEnd('fieldset'); + + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + } + + function message($msg) + { + $this->message_text = $msg; + $this->showPage(); + } + + function createNewUser() + { + if (common_config('site', 'closed')) { + $this->clientError(_('Registration not allowed.')); + return; + } + + $invite = null; + + if (common_config('site', 'inviteonly')) { + $code = $_SESSION['invitecode']; + if (empty($code)) { + $this->clientError(_('Registration not allowed.')); + return; + } + + $invite = Invitation::staticGet($code); + + if (empty($invite)) { + $this->clientError(_('Not a valid invitation code.')); + return; + } + } + + $nickname = $this->trimmed('newname'); + + if (!Validate::string($nickname, array('min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT))) { + $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.')); + return; + } + + if (!User::allowed_nickname($nickname)) { + $this->showForm(_('Nickname not allowed.')); + return; + } + + if (User::staticGet('nickname', $nickname)) { + $this->showForm(_('Nickname already in use. Try another one.')); + return; + } + + $fullname = trim($this->tw_fields['fullname']); + + $args = array('nickname' => $nickname, 'fullname' => $fullname); + + if (!empty($invite)) { + $args['code'] = $invite->code; + } + + $user = User::register($args); + + $result = $this->flinkUser($user->id, $this->twuid); + + if (!$result) { + $this->serverError(_('Error connecting user to Twitter.')); + return; + } + + common_set_user($user); + common_real_login(true); + + common_debug('Twitter Connect Plugin - ' . + "Registered new user $user->id from Twitter user $this->fbuid"); + + common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), + 303); + } + + function connectNewUser() + { + $nickname = $this->trimmed('nickname'); + $password = $this->trimmed('password'); + + if (!common_check_user($nickname, $password)) { + $this->showForm(_('Invalid username or password.')); + return; + } + + $user = User::staticGet('nickname', $nickname); + + if (!empty($user)) { + common_debug('Twitter Connect Plugin - ' . + "Legit user to connect to Twitter: $nickname"); + } + + $result = $this->flinkUser($user->id, $this->twuid); + + if (!$result) { + $this->serverError(_('Error connecting user to Twitter.')); + return; + } + + common_debug('Twitter Connnect Plugin - ' . + "Connected Twitter user $this->fbuid to local user $user->id"); + + common_set_user($user); + common_real_login(true); + + $this->goHome($user->nickname); + } + + function connectUser() + { + $user = common_current_user(); + + $result = $this->flinkUser($user->id, $this->twuid); + + if (empty($result)) { + $this->serverError(_('Error connecting user to Twitter.')); + return; + } + + common_debug('Twitter Connect Plugin - ' . + "Connected Twitter user $this->fbuid to local user $user->id"); + + // Return to Twitter connection settings tab + common_redirect(common_local_url('twittersettings'), 303); + } + + function tryLogin() + { + common_debug('Twitter Connect Plugin - ' . + "Trying login for Twitter user $this->fbuid."); + + $flink = Foreign_link::getByForeignID($this->twuid, TWITTER_SERVICE); + + if (!empty($flink)) { + $user = $flink->getUser(); + + if (!empty($user)) { + + common_debug('Twitter Connect Plugin - ' . + "Logged in Twitter user $flink->foreign_id as user $user->id ($user->nickname)"); + + common_set_user($user); + common_real_login(true); + $this->goHome($user->nickname); + } + + } else { + + common_debug('Twitter Connect Plugin - ' . + "No flink found for twuid: $this->twuid - new user"); + + $this->showForm(null, $this->bestNewNickname()); + } + } + + function goHome($nickname) + { + $url = common_get_returnto(); + if ($url) { + // We don't have to return to it again + common_set_returnto(null); + } else { + $url = common_local_url('all', + array('nickname' => + $nickname)); + } + + common_redirect($url, 303); + } + + function flinkUser($user_id, $twuid) + { + $flink = new Foreign_link(); + + $flink->user_id = $user_id; + $flink->foreign_id = $twuid; + $flink->service = TWITTER_SERVICE; + + $creds = TwitterOAuthClient::packToken($this->access_token); + + $flink->credentials = $creds; + $flink->created = common_sql_now(); + + // Defaults: noticesync on, everything else off + + $flink->set_flags(true, false, false, false); + + $flink_id = $flink->insert(); + + if (empty($flink_id)) { + common_log_db_error($flink, 'INSERT', __FILE__); + $this->serverError(_('Couldn\'t link your Twitter account.')); + } + + save_twitter_user($twuid, $this->tw_fields['name']); + + return $flink_id; + } + + + function bestNewNickname() + { + if (!empty($this->tw_fields['name'])) { + $nickname = $this->nicknamize($this->tw_fields['name']); + if ($this->isNewNickname($nickname)) { + return $nickname; + } + } + + return null; + } + + // Given a string, try to make it work as a nickname + + function nicknamize($str) + { + $str = preg_replace('/\W/', '', $str); + $str = str_replace(array('-', '_'), '', $str); + return strtolower($str); + } + + function isNewNickname($str) + { + if (!Validate::string($str, array('min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT))) { + return false; + } + if (!User::allowed_nickname($str)) { + return false; + } + if (User::staticGet('nickname', $str)) { + return false; + } + return true; + } + } diff --git a/plugins/TwitterBridge/twitterlogin.php b/plugins/TwitterBridge/twitterlogin.php new file mode 100644 index 000000000..ae468ea15 --- /dev/null +++ b/plugins/TwitterBridge/twitterlogin.php @@ -0,0 +1,95 @@ +. + * + * @category Settings + * @package StatusNet + * @author Evan Prodromou + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; + +/** + * Settings for Twitter integration + * + * @category Settings + * @package StatusNet + * @author Evan Prodromou + * @author Julien Chaumond + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see SettingsAction + */ + + +class TwitterloginAction extends Action +{ + function handle($args) + { + parent::handle($args); + + if (common_is_real_login()) { + $this->clientError(_('Already logged in.')); + } + + $this->showPage(); + } + + function title() + { + return _('Twitter Login'); + } + + function getInstructions() + { + return _('Login with your Twitter Account'); + } + + function showPageNotice() + { + $instr = $this->getInstructions(); + $output = common_markup_to_html($instr); + $this->elementStart('div', 'instructions'); + $this->raw($output); + $this->elementEnd('div'); + } + + function showContent() + { + $this->elementStart('a', array('href' => common_local_url('twitterauthorization'))); + $this->element('img', array('src' => common_path('plugins/TwitterBridge/twitter_connect.gif'), + 'alt' => 'Connect my Twitter account')); + $this->elementEnd('a'); + } + + function showLocalNav() + { + $nav = new LoginGroupNav($this); + $nav->show(); + } +} -- cgit v1.2.3-54-g00ecf From 2d97e15cd6b5fbda82218b97ce287a56e2665fb9 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 01:25:33 +0000 Subject: - Twitter username wasn't getting stored in Foreign_user when linking Twitter account (fixed) - Updates to comments --- plugins/TwitterBridge/twitterauthorization.php | 109 +++++++++++++------------ 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index 8ae725374..3f7316b7a 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -53,7 +53,7 @@ class TwitterauthorizationAction extends Action var $twuid = null; var $tw_fields = null; var $access_token = null; - + /** * Initialize class members. Looks for 'oauth_token' parameter. * @@ -80,31 +80,37 @@ class TwitterauthorizationAction extends Action function handle($args) { parent::handle($args); - + if (common_logged_in()) { $user = common_current_user(); $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); - + // If there's already a foreign link record, it means we already // have an access token, and this is unecessary. So go back. - + if (isset($flink)) { common_redirect(common_local_url('twittersettings')); } } - - + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + // User was not logged in to StatusNet before + $this->twuid = $this->trimmed('twuid'); - $this->tw_fields = array("name" => $this->trimmed('name'), "fullname" => $this->trimmed('fullname')); + + $this->tw_fields = array('name' => $this->trimmed('tw_fields_name'), + 'fullname' => $this->trimmed('tw_fields_fullname')); + $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret')); $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { $this->showForm(_('There was a problem with your session token. Try again, please.')); return; } + if ($this->arg('create')) { if (!$this->boolean('license')) { $this->showForm(_('You can\'t register if you don\'t agree to the license.'), @@ -124,7 +130,7 @@ class TwitterauthorizationAction extends Action // $this->oauth_token is only populated once Twitter authorizes our // request token. If it's empty we're at the beginning of the auth // process - + if (empty($this->oauth_token)) { $this->authorizeRequestToken(); } else { @@ -181,6 +187,8 @@ class TwitterauthorizationAction extends Action $this->serverError(_m('Couldn\'t link your Twitter account.')); } + $twitter_user = null; + try { $client = new TwitterOAuthClient($_SESSION['twitter_request_token'], @@ -196,16 +204,19 @@ class TwitterauthorizationAction extends Action $twitter_user = $client->verifyCredentials(); } catch (OAuthClientException $e) { - $msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s', + $msg = sprintf('OAuth client error - code: %1$s, msg: %2$s', $e->getCode(), $e->getMessage()); $this->serverError(_m('Couldn\'t link your Twitter account.')); } if (common_logged_in()) { + // Save the access token and Twitter user info + $this->saveForeignLink($atok, $twitter_user); - } - else{ + + } else { + $this->twuid = $twitter_user->id; $this->tw_fields = array("name" => $twitter_user->screen_name, "fullname" => $twitter_user->name); $this->access_token = $atok; @@ -216,7 +227,7 @@ class TwitterauthorizationAction extends Action unset($_SESSION['twitter_request_token']); unset($_SESSION['twitter_request_token_secret']); - + if (common_logged_in()) { common_redirect(common_local_url('twittersettings')); } @@ -260,8 +271,34 @@ class TwitterauthorizationAction extends Action save_twitter_user($twitter_user->id, $twitter_user->screen_name); } + function flinkUser($user_id, $twuid) + { + $flink = new Foreign_link(); + + $flink->user_id = $user_id; + $flink->foreign_id = $twuid; + $flink->service = TWITTER_SERVICE; + + $creds = TwitterOAuthClient::packToken($this->access_token); + $flink->credentials = $creds; + $flink->created = common_sql_now(); + // Defaults: noticesync on, everything else off + + $flink->set_flags(true, false, false, false); + + $flink_id = $flink->insert(); + + if (empty($flink_id)) { + common_log_db_error($flink, 'INSERT', __FILE__); + $this->serverError(_('Couldn\'t link your Twitter account.')); + } + + save_twitter_user($twuid, $this->tw_fields['name']); + + return $flink_id; + } function showPageNotice() { @@ -430,7 +467,7 @@ class TwitterauthorizationAction extends Action common_set_user($user); common_real_login(true); - common_debug('Twitter Connect Plugin - ' . + common_debug('TwitterBridge Plugin - ' . "Registered new user $user->id from Twitter user $this->fbuid"); common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), @@ -450,7 +487,7 @@ class TwitterauthorizationAction extends Action $user = User::staticGet('nickname', $nickname); if (!empty($user)) { - common_debug('Twitter Connect Plugin - ' . + common_debug('TwitterBridge Plugin - ' . "Legit user to connect to Twitter: $nickname"); } @@ -461,7 +498,7 @@ class TwitterauthorizationAction extends Action return; } - common_debug('Twitter Connnect Plugin - ' . + common_debug('TwitterBridge Plugin - ' . "Connected Twitter user $this->fbuid to local user $user->id"); common_set_user($user); @@ -481,17 +518,17 @@ class TwitterauthorizationAction extends Action return; } - common_debug('Twitter Connect Plugin - ' . + common_debug('TwitterBridge Plugin - ' . "Connected Twitter user $this->fbuid to local user $user->id"); // Return to Twitter connection settings tab common_redirect(common_local_url('twittersettings'), 303); } - + function tryLogin() { - common_debug('Twitter Connect Plugin - ' . - "Trying login for Twitter user $this->fbuid."); + common_debug('TwitterBridge Plugin - ' . + "Trying login for Twitter user $this->twuid."); $flink = Foreign_link::getByForeignID($this->twuid, TWITTER_SERVICE); @@ -500,7 +537,7 @@ class TwitterauthorizationAction extends Action if (!empty($user)) { - common_debug('Twitter Connect Plugin - ' . + common_debug('TwitterBridge Plugin - ' . "Logged in Twitter user $flink->foreign_id as user $user->id ($user->nickname)"); common_set_user($user); @@ -510,7 +547,7 @@ class TwitterauthorizationAction extends Action } else { - common_debug('Twitter Connect Plugin - ' . + common_debug('TwitterBridge Plugin - ' . "No flink found for twuid: $this->twuid - new user"); $this->showForm(null, $this->bestNewNickname()); @@ -531,37 +568,7 @@ class TwitterauthorizationAction extends Action common_redirect($url, 303); } - - function flinkUser($user_id, $twuid) - { - $flink = new Foreign_link(); - - $flink->user_id = $user_id; - $flink->foreign_id = $twuid; - $flink->service = TWITTER_SERVICE; - - $creds = TwitterOAuthClient::packToken($this->access_token); - - $flink->credentials = $creds; - $flink->created = common_sql_now(); - - // Defaults: noticesync on, everything else off - - $flink->set_flags(true, false, false, false); - - $flink_id = $flink->insert(); - if (empty($flink_id)) { - common_log_db_error($flink, 'INSERT', __FILE__); - $this->serverError(_('Couldn\'t link your Twitter account.')); - } - - save_twitter_user($twuid, $this->tw_fields['name']); - - return $flink_id; - } - - function bestNewNickname() { if (!empty($this->tw_fields['name'])) { -- cgit v1.2.3-54-g00ecf From 1c1abfc284c3b81cfcd4cd95b80a0e5c120bd962 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 01:51:40 +0000 Subject: Ask the user to set a password before disconnecting from Twitter --- plugins/TwitterBridge/twittersettings.php | 33 ++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/plugins/TwitterBridge/twittersettings.php b/plugins/TwitterBridge/twittersettings.php index bc9a636a1..0137060e9 100644 --- a/plugins/TwitterBridge/twittersettings.php +++ b/plugins/TwitterBridge/twittersettings.php @@ -121,8 +121,35 @@ class TwittersettingsAction extends ConnectSettingsAction $this->elementEnd('p'); $this->element('p', 'form_note', _m('Connected Twitter account')); + $this->elementEnd('fieldset'); + + $this->elementStart('fieldset'); + + $this->element('legend', null, _m('Disconnect my account from Twitter')); - $this->submit('remove', _m('Remove')); + if (!$user->password) { + + $this->elementStart('p', array('class' => 'form_guide')); + $this->text(_m('Disconnecting your Twitter ' . + 'could make it impossible to log in! Please ')); + $this->element('a', + array('href' => common_local_url('passwordsettings')), + _m('set a password')); + + $this->text(_m(' first.')); + $this->elementEnd('p'); + } else { + + $note = _m('Keep your %1$s account but disconnect from Twitter. ' . + 'You can use your %1$s password to log in.'); + + $site = common_config('site', 'name'); + + $this->element('p', 'instructions', + sprintf($note, $site)); + + $this->submit('disconnect', _m('Disconnect')); + } $this->elementEnd('fieldset'); @@ -205,7 +232,7 @@ class TwittersettingsAction extends ConnectSettingsAction if ($this->arg('save')) { $this->savePreferences(); - } else if ($this->arg('remove')) { + } else if ($this->arg('disconnect')) { $this->removeTwitterAccount(); } else { $this->showForm(_m('Unexpected form submission.')); @@ -231,7 +258,7 @@ class TwittersettingsAction extends ConnectSettingsAction return; } - $this->showForm(_m('Twitter account removed.'), true); + $this->showForm(_m('Twitter account disconnected.'), true); } /** -- cgit v1.2.3-54-g00ecf From ce44008d13e39d3e3a95af7d422a82f5ecce61e0 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 02:40:44 +0000 Subject: Use "Sign in with Twitter" auth pattern and official Twitter button for Twitter-based login. See: http://apiwiki.twitter.com/Sign-in-with-Twitter --- plugins/TwitterBridge/twitter_connect.gif | Bin 2205 -> 0 bytes plugins/TwitterBridge/twitterauthorization.php | 8 +++++--- plugins/TwitterBridge/twitterlogin.php | 11 ++++++----- plugins/TwitterBridge/twitteroauthclient.php | 7 +++++-- 4 files changed, 16 insertions(+), 10 deletions(-) delete mode 100644 plugins/TwitterBridge/twitter_connect.gif diff --git a/plugins/TwitterBridge/twitter_connect.gif b/plugins/TwitterBridge/twitter_connect.gif deleted file mode 100644 index e3d8f3ed7..000000000 Binary files a/plugins/TwitterBridge/twitter_connect.gif and /dev/null differ diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index 3f7316b7a..15408668f 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -50,9 +50,10 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; */ class TwitterauthorizationAction extends Action { - var $twuid = null; - var $tw_fields = null; + var $twuid = null; + var $tw_fields = null; var $access_token = null; + var $signin = null; /** * Initialize class members. Looks for 'oauth_token' parameter. @@ -65,6 +66,7 @@ class TwitterauthorizationAction extends Action { parent::prepare($args); + $this->signin = $this->boolean('signin'); $this->oauth_token = $this->arg('oauth_token'); return true; @@ -160,7 +162,7 @@ class TwitterauthorizationAction extends Action $_SESSION['twitter_request_token'] = $req_tok->key; $_SESSION['twitter_request_token_secret'] = $req_tok->secret; - $auth_link = $client->getAuthorizeLink($req_tok); + $auth_link = $client->getAuthorizeLink($req_tok, $this->signin); } catch (OAuthClientException $e) { $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s', diff --git a/plugins/TwitterBridge/twitterlogin.php b/plugins/TwitterBridge/twitterlogin.php index ae468ea15..ae67b4c15 100644 --- a/plugins/TwitterBridge/twitterlogin.php +++ b/plugins/TwitterBridge/twitterlogin.php @@ -46,7 +46,6 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; * @see SettingsAction */ - class TwitterloginAction extends Action { function handle($args) @@ -67,7 +66,7 @@ class TwitterloginAction extends Action function getInstructions() { - return _('Login with your Twitter Account'); + return _('Login with your Twitter account'); } function showPageNotice() @@ -81,9 +80,11 @@ class TwitterloginAction extends Action function showContent() { - $this->elementStart('a', array('href' => common_local_url('twitterauthorization'))); - $this->element('img', array('src' => common_path('plugins/TwitterBridge/twitter_connect.gif'), - 'alt' => 'Connect my Twitter account')); + $this->elementStart('a', array('href' => common_local_url('twitterauthorization', + null, + array('signin' => true)))); + $this->element('img', array('src' => common_path('plugins/TwitterBridge/Sign-in-with-Twitter-lighter.png'), + 'alt' => 'Sign in with Twitter')); $this->elementEnd('a'); } diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php index bad2b74ca..277e7ab40 100644 --- a/plugins/TwitterBridge/twitteroauthclient.php +++ b/plugins/TwitterBridge/twitteroauthclient.php @@ -45,6 +45,7 @@ class TwitterOAuthClient extends OAuthClient { public static $requestTokenURL = 'https://twitter.com/oauth/request_token'; public static $authorizeURL = 'https://twitter.com/oauth/authorize'; + public static $signinUrl = 'https://twitter.com/oauth/authenticate'; public static $accessTokenURL = 'https://twitter.com/oauth/access_token'; /** @@ -97,9 +98,11 @@ class TwitterOAuthClient extends OAuthClient * * @return the link */ - function getAuthorizeLink($request_token) + function getAuthorizeLink($request_token, $signin = false) { - return parent::getAuthorizeLink(self::$authorizeURL, + $url = ($signin) ? self::$signinUrl : self::$authorizeURL; + + return parent::getAuthorizeLink($url, $request_token, common_local_url('twitterauthorization')); } -- cgit v1.2.3-54-g00ecf From f7450d2ca8b420260caf69bf1cded687d8eaf7b5 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 07:29:40 +0000 Subject: - Remove redundant function - clean up log msgs --- plugins/TwitterBridge/twitterauthorization.php | 74 ++++++++++---------------- 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index 15408668f..a95cdebb9 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -101,7 +101,7 @@ class TwitterauthorizationAction extends Action $this->twuid = $this->trimmed('twuid'); - $this->tw_fields = array('name' => $this->trimmed('tw_fields_name'), + $this->tw_fields = array('screen_name' => $this->trimmed('tw_fields_screen_name'), 'fullname' => $this->trimmed('tw_fields_fullname')); $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret')); @@ -215,12 +215,15 @@ class TwitterauthorizationAction extends Action // Save the access token and Twitter user info - $this->saveForeignLink($atok, $twitter_user); + $user = common_current_user(); + $this->saveForeignLink($user->id, $twitter_user->id, $atok); + save_twitter_user($twitter_user->id, $twitter_user->name); } else { $this->twuid = $twitter_user->id; - $this->tw_fields = array("name" => $twitter_user->screen_name, "fullname" => $twitter_user->name); + $this->tw_fields = array("screen_name" => $twitter_user->screen_name, + "name" => $twitter_user->name); $this->access_token = $atok; $this->tryLogin(); } @@ -239,41 +242,13 @@ class TwitterauthorizationAction extends Action * Saves a Foreign_link between Twitter user and local user, * which includes the access token and secret. * - * @param OAuthToken $access_token the access token to save - * @param mixed $twitter_user twitter API user object + * @param int $user_id StatusNet user ID + * @param int $twuid Twitter user ID + * @param OAuthToken $token the access token to save * * @return nothing */ - function saveForeignLink($access_token, $twitter_user) - { - $user = common_current_user(); - - $flink = new Foreign_link(); - - $flink->user_id = $user->id; - $flink->foreign_id = $twitter_user->id; - $flink->service = TWITTER_SERVICE; - - $creds = TwitterOAuthClient::packToken($access_token); - - $flink->credentials = $creds; - $flink->created = common_sql_now(); - - // Defaults: noticesync on, everything else off - - $flink->set_flags(true, false, false, false); - - $flink_id = $flink->insert(); - - if (empty($flink_id)) { - common_log_db_error($flink, 'INSERT', __FILE__); - $this->serverError(_m('Couldn\'t link your Twitter account.')); - } - - save_twitter_user($twitter_user->id, $twitter_user->screen_name); - } - - function flinkUser($user_id, $twuid) + function saveForeignLink($user_id, $twuid, $access_token) { $flink = new Foreign_link(); @@ -281,7 +256,7 @@ class TwitterauthorizationAction extends Action $flink->foreign_id = $twuid; $flink->service = TWITTER_SERVICE; - $creds = TwitterOAuthClient::packToken($this->access_token); + $creds = TwitterOAuthClient::packToken($access_token); $flink->credentials = $creds; $flink->created = common_sql_now(); @@ -297,8 +272,6 @@ class TwitterauthorizationAction extends Action $this->serverError(_('Couldn\'t link your Twitter account.')); } - save_twitter_user($twuid, $this->tw_fields['name']); - return $flink_id; } @@ -361,8 +334,8 @@ class TwitterauthorizationAction extends Action $this->hidden('access_token_key', $this->access_token->key); $this->hidden('access_token_secret', $this->access_token->secret); $this->hidden('twuid', $this->twuid); + $this->hidden('tw_fields_screen_name', $this->tw_fields['screen_name']); $this->hidden('tw_fields_name', $this->tw_fields['name']); - $this->hidden('tw_fields_fullname', $this->tw_fields['fullname']); $this->elementStart('fieldset'); $this->hidden('token', common_session_token()); @@ -449,7 +422,7 @@ class TwitterauthorizationAction extends Action return; } - $fullname = trim($this->tw_fields['fullname']); + $fullname = trim($this->tw_fields['name']); $args = array('nickname' => $nickname, 'fullname' => $fullname); @@ -459,7 +432,11 @@ class TwitterauthorizationAction extends Action $user = User::register($args); - $result = $this->flinkUser($user->id, $this->twuid); + $result = $this->saveForeignLink($user->id, + $this->twuid, + $this->access_token); + + save_twitter_user($this->twuid, $this->tw_fields['screen_name']); if (!$result) { $this->serverError(_('Error connecting user to Twitter.')); @@ -470,7 +447,7 @@ class TwitterauthorizationAction extends Action common_real_login(true); common_debug('TwitterBridge Plugin - ' . - "Registered new user $user->id from Twitter user $this->fbuid"); + "Registered new user $user->id from Twitter user $this->twuid"); common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), 303); @@ -493,7 +470,11 @@ class TwitterauthorizationAction extends Action "Legit user to connect to Twitter: $nickname"); } - $result = $this->flinkUser($user->id, $this->twuid); + $result = $this->saveForeignLink($user->id, + $this->twuid, + $this->access_token); + + save_twitter_user($this->twuid, $this->tw_fields['screen_name']); if (!$result) { $this->serverError(_('Error connecting user to Twitter.')); @@ -501,7 +482,7 @@ class TwitterauthorizationAction extends Action } common_debug('TwitterBridge Plugin - ' . - "Connected Twitter user $this->fbuid to local user $user->id"); + "Connected Twitter user $this->twuid to local user $user->id"); common_set_user($user); common_real_login(true); @@ -521,7 +502,7 @@ class TwitterauthorizationAction extends Action } common_debug('TwitterBridge Plugin - ' . - "Connected Twitter user $this->fbuid to local user $user->id"); + "Connected Twitter user $this->twuid to local user $user->id"); // Return to Twitter connection settings tab common_redirect(common_local_url('twittersettings'), 303); @@ -532,7 +513,8 @@ class TwitterauthorizationAction extends Action common_debug('TwitterBridge Plugin - ' . "Trying login for Twitter user $this->twuid."); - $flink = Foreign_link::getByForeignID($this->twuid, TWITTER_SERVICE); + $flink = Foreign_link::getByForeignID($this->twuid, + TWITTER_SERVICE); if (!empty($flink)) { $user = $flink->getUser(); -- cgit v1.2.3-54-g00ecf From 02957d28541b3164df117d54788d1152c85ff00b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 07:50:01 +0000 Subject: Add Julien C to author comments --- plugins/TwitterBridge/TwitterBridgePlugin.php | 12 +++++------- plugins/TwitterBridge/twitterauthorization.php | 8 +++++--- plugins/TwitterBridge/twitterlogin.php | 15 ++++++++------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index e39ec7be0..c7f57ffc7 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -20,7 +20,8 @@ * @category Plugin * @package StatusNet * @author Zach Copley - * @copyright 2009 Control Yourself, Inc. + * @author Julien C + * @copyright 2009-2010 Control Yourself, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ */ @@ -41,6 +42,7 @@ define('TWITTERBRIDGEPLUGIN_VERSION', '0.9'); * @category Plugin * @package StatusNet * @author Zach Copley + * @author Julien C * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ * @link http://twitter.com/ @@ -72,16 +74,13 @@ class TwitterBridgePlugin extends Plugin $m->connect('twitter/authorization', array('action' => 'twitterauthorization')); $m->connect('settings/twitter', array('action' => 'twittersettings')); - $m->connect('main/twitterlogin', array('action' => 'twitterlogin')); return true; } - - - + /* - * Add a login tab for Twitter Connect + * Add a login tab for 'Sign in with Twitter' * * @param Action &action the current action * @@ -99,7 +98,6 @@ class TwitterBridgePlugin extends Plugin return true; } - /** * Add the Twitter Settings page to the Connect Settings menu diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index a95cdebb9..b2657ff61 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -19,10 +19,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category TwitterauthorizationAction + * @category Plugin * @package StatusNet * @author Zach Copley - * @copyright 2009 StatusNet, Inc. + * @author Julien C + * @copyright 2009-2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -41,9 +42,10 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; * (Foreign_link) between the StatusNet user and Twitter user and stores the * access token and secret in the link. * - * @category Twitter + * @category Plugin * @package StatusNet * @author Zach Copley + * @author Julien C * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ * diff --git a/plugins/TwitterBridge/twitterlogin.php b/plugins/TwitterBridge/twitterlogin.php index ae67b4c15..79421fb27 100644 --- a/plugins/TwitterBridge/twitterlogin.php +++ b/plugins/TwitterBridge/twitterlogin.php @@ -2,7 +2,7 @@ /** * StatusNet, the distributed open-source microblogging tool * - * Settings for Twitter integration + * 'Sign in with Twitter' login page * * PHP version 5 * @@ -19,10 +19,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category Settings + * @category Login * @package StatusNet - * @author Evan Prodromou - * @copyright 2008-2009 StatusNet, Inc. + * @author Julien Chaumond + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -34,12 +35,12 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; /** - * Settings for Twitter integration + * Page for logging in with Twitter * - * @category Settings + * @category Login * @package StatusNet - * @author Evan Prodromou * @author Julien Chaumond + * @author Zach Copley * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ * -- cgit v1.2.3-54-g00ecf From e05c32572234dc0b4adb19be7bbe9879f49ec7ee Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 26 Jan 2010 19:13:05 +0100 Subject: Updated geolocation sharing in notice form for Realtime pop --- plugins/Realtime/realtimeupdate.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Realtime/realtimeupdate.css b/plugins/Realtime/realtimeupdate.css index 56f869354..31e7c2ae6 100644 --- a/plugins/Realtime/realtimeupdate.css +++ b/plugins/Realtime/realtimeupdate.css @@ -18,7 +18,8 @@ display:none; } .realtime-popup #form_notice label[for=notice_data-attach], -.realtime-popup #form_notice #notice_data-attach { +.realtime-popup #form_notice #notice_data-attach, +.realtime-popup #form_notice label[for=notice_data-geo] { top:0; } -- cgit v1.2.3-54-g00ecf From ad6f0501ff24cb287dedd21271d58c91e64b6b43 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 26 Jan 2010 09:25:39 -0800 Subject: Site metadata tags in status_network: single 'tags' field, pipe-separated. $sn->tags() returns tag list as array; $sn->hasTag('blah') to check for a particular tag only Could be used to control things in config file: $sn = Status_network::setupSite($_server, $_path, $_wildcard); if (!$sn) { die("No such site"); } if ($sn->hasTag('individual')) { /* blah */ } Note memcached keys are unchanged; if tags are changed from an external tool clear: statusnet::status_network:: for s 'nickname', 'hostname', and 'pathname' --- classes/Status_network.php | 20 ++++++++++++++++++++ db/site.sql | 2 ++ 2 files changed, 22 insertions(+) diff --git a/classes/Status_network.php b/classes/Status_network.php index 445f8a5a3..f1314d615 100644 --- a/classes/Status_network.php +++ b/classes/Status_network.php @@ -39,6 +39,7 @@ class Status_network extends DB_DataObject public $logo; // varchar(255) public $created; // datetime() not_null public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + public $tags; // text /* Static get */ function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Status_network',$k,$v); } @@ -245,4 +246,23 @@ class Status_network extends DB_DataObject return $this->nickname . '.' . self::$wildcard; } } + + /** + * Return site meta-info tags as an array + * @return array of strings + */ + function getTags() + { + return array_filter(explode("|", strval($this->tags))); + } + + /** + * Check if this site record has a particular meta-info tag attached. + * @param string $tag + * @return bool + */ + function hasTag($tag) + { + return in_array($tag, $this->getTags()); + } } diff --git a/db/site.sql b/db/site.sql index a9f64e5a5..791303bd5 100644 --- a/db/site.sql +++ b/db/site.sql @@ -14,6 +14,8 @@ create table status_network ( sitename varchar(255) comment 'display name', theme varchar(255) comment 'theme name', logo varchar(255) comment 'site logo', + + tags text comment 'site meta-info tags (pipe-separated)', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified' -- cgit v1.2.3-54-g00ecf From 58be61b6417119de1b03ef50e166369c4005e4d1 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 26 Jan 2010 11:49:49 -0800 Subject: Control channel for queue daemons to request graceful shutdown, restart, or update to listen to a newly added or reconfigured site. queuectl.php --update -s queuectl.php --stop queuectl.php --restart Default control channel is /topic/statusnet-control. For external utilities to send a site update ping direct to the queue server, connect via Stomp and send a message formatted thus: update: (Nickname here, *not* server hostname! The rest of the queues will be updated to use nicknames later.) Note that all currently-connected queue daemons will get these notifications, including both queuedaemon.php and xmppdaemon.php. (XMPP will ignore site update requests for sites that it's not handling.) Limitations: * only implemented for stomp queue manager so far * --update may not yet handle a changed server name properly * --restart won't reload PHP code files that were already loaded at startup. Still need to stop and restart the daemons from 'outside' when updating code base. --- classes/Status_network.php | 11 +++- lib/default.php | 1 + lib/iomaster.php | 53 +++++++++++---- lib/queuemanager.php | 17 +++++ lib/spawningdaemon.php | 58 +++++++++++++---- lib/stompqueuemanager.php | 159 +++++++++++++++++++++++++++++++++++++++++---- scripts/queuectl.php | 85 ++++++++++++++++++++++++ scripts/queuedaemon.php | 2 +- scripts/xmppdaemon.php | 2 +- 9 files changed, 347 insertions(+), 41 deletions(-) create mode 100755 scripts/queuectl.php diff --git a/classes/Status_network.php b/classes/Status_network.php index f1314d615..4bda24b6a 100644 --- a/classes/Status_network.php +++ b/classes/Status_network.php @@ -42,7 +42,16 @@ class Status_network extends DB_DataObject public $tags; // text /* Static get */ - function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Status_network',$k,$v); } + function staticGet($k,$v=NULL) { + $i = DB_DataObject::staticGet('Status_network',$k,$v); + + // Don't use local process cache; if we're fetching multiple + // times it's because we're reloading it in a long-running + // process; we need a fresh copy! + global $_DB_DATAOBJECT; + unset($_DB_DATAOBJECT['CACHE']['status_network']); + return $i; + } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/lib/default.php b/lib/default.php index 35115542f..d2dd8ab33 100644 --- a/lib/default.php +++ b/lib/default.php @@ -81,6 +81,7 @@ $default = 'subsystem' => 'db', # default to database, or 'stomp' 'stomp_server' => null, 'queue_basename' => '/queue/statusnet/', + 'control_channel' => '/topic/statusnet-control', // broadcasts to all queue daemons 'stomp_username' => null, 'stomp_password' => null, 'monitor' => null, // URL to monitor ping endpoint (work in progress) diff --git a/lib/iomaster.php b/lib/iomaster.php index 3bf82bc6b..bcab3542b 100644 --- a/lib/iomaster.php +++ b/lib/iomaster.php @@ -38,6 +38,9 @@ abstract class IoMaster protected $pollTimeouts = array(); protected $lastPoll = array(); + public $shutdown = false; // Did we do a graceful shutdown? + public $respawn = true; // Should we respawn after shutdown? + /** * @param string $id process ID to use in logging/monitoring */ @@ -144,7 +147,7 @@ abstract class IoMaster $this->logState('init'); $this->start(); - while (true) { + while (!$this->shutdown) { $timeouts = array_values($this->pollTimeouts); $timeouts[] = 60; // default max timeout @@ -196,22 +199,31 @@ abstract class IoMaster $this->logState('idle'); $this->idle(); - $memoryLimit = $this->softMemoryLimit(); - if ($memoryLimit > 0) { - $usage = memory_get_usage(); - if ($usage > $memoryLimit) { - common_log(LOG_INFO, "Queue thread hit soft memory limit ($usage > $memoryLimit); gracefully restarting."); - break; - } else if (common_config('queue', 'debug_memory')) { - common_log(LOG_DEBUG, "Memory usage $usage"); - } - } + $this->checkMemory(); } $this->logState('shutdown'); $this->finish(); } + /** + * Check runtime memory usage, possibly triggering a graceful shutdown + * and thread respawn if we've crossed the soft limit. + */ + protected function checkMemory() + { + $memoryLimit = $this->softMemoryLimit(); + if ($memoryLimit > 0) { + $usage = memory_get_usage(); + if ($usage > $memoryLimit) { + common_log(LOG_INFO, "Queue thread hit soft memory limit ($usage > $memoryLimit); gracefully restarting."); + $this->requestRestart(); + } else if (common_config('queue', 'debug_memory')) { + common_log(LOG_DEBUG, "Memory usage $usage"); + } + } + } + /** * Return fully-parsed soft memory limit in bytes. * @return intval 0 or -1 if not set @@ -354,5 +366,24 @@ abstract class IoMaster $owners[] = "thread:" . $this->id; $this->monitor->stats($key, $owners); } + + /** + * For IoManagers to request a graceful shutdown at end of event loop. + */ + public function requestShutdown() + { + $this->shutdown = true; + $this->respawn = false; + } + + /** + * For IoManagers to request a graceful restart at end of event loop. + */ + public function requestRestart() + { + $this->shutdown = true; + $this->respawn = true; + } + } diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 1bc8de1f7..afe710e88 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -100,6 +100,23 @@ abstract class QueueManager extends IoManager $this->initialize(); } + /** + * Optional; ping any running queue handler daemons with a notification + * such as announcing a new site to handle or requesting clean shutdown. + * This avoids having to restart all the daemons manually to update configs + * and such. + * + * Called from scripts/queuectl.php controller utility. + * + * @param string $event event key + * @param string $param optional parameter to append to key + * @return boolean success + */ + public function sendControlSignal($event, $param='') + { + throw new Exception(get_class($this) . " does not support control signals."); + } + /** * Store an object (usually/always a Notice) into the given queue * for later processing. No guarantee is made on when it will be diff --git a/lib/spawningdaemon.php b/lib/spawningdaemon.php index 8baefe88e..b1961d688 100644 --- a/lib/spawningdaemon.php +++ b/lib/spawningdaemon.php @@ -36,6 +36,11 @@ abstract class SpawningDaemon extends Daemon { protected $threads=1; + const EXIT_OK = 0; + const EXIT_ERR = 1; + const EXIT_SHUTDOWN = 100; + const EXIT_RESTART = 101; + function __construct($id=null, $daemonize=true, $threads=1) { parent::__construct($daemonize); @@ -49,7 +54,7 @@ abstract class SpawningDaemon extends Daemon /** * Perform some actual work! * - * @return boolean true on success, false on failure + * @return int exit code; use self::EXIT_SHUTDOWN to request not to respawn. */ public abstract function runThread(); @@ -84,23 +89,30 @@ abstract class SpawningDaemon extends Daemon while (count($children) > 0) { $status = null; $pid = pcntl_wait($status); - if ($pid > 0) { + if ($pid > 0 && pcntl_wifexited($status)) { + $exitCode = pcntl_wexitstatus($status); + $i = array_search($pid, $children); if ($i === false) { - $this->log(LOG_ERR, "Unrecognized child pid $pid exited!"); + $this->log(LOG_ERR, "Unrecognized child pid $pid exited with status $exitCode"); continue; } unset($children[$i]); - $this->log(LOG_INFO, "Thread $i pid $pid exited."); - - $pid = pcntl_fork(); - if ($pid < 0) { - $this->log(LOG_ERROR, "Couldn't fork to respawn thread $i; aborting thread.\n"); - } else if ($pid == 0) { - $this->initAndRunChild($i); + + if ($this->shouldRespawn($exitCode)) { + $this->log(LOG_INFO, "Thread $i pid $pid exited with status $exitCode; respawing."); + + $pid = pcntl_fork(); + if ($pid < 0) { + $this->log(LOG_ERROR, "Couldn't fork to respawn thread $i; aborting thread.\n"); + } else if ($pid == 0) { + $this->initAndRunChild($i); + } else { + $this->log(LOG_INFO, "Respawned thread $i as pid $pid"); + $children[$i] = $pid; + } } else { - $this->log(LOG_INFO, "Respawned thread $i as pid $pid"); - $children[$i] = $pid; + $this->log(LOG_INFO, "Thread $i pid $pid exited with status $exitCode; closing out thread."); } } } @@ -108,6 +120,24 @@ abstract class SpawningDaemon extends Daemon return true; } + /** + * Determine whether to respawn an exited subprocess based on its exit code. + * Otherwise we'll respawn all exits by default. + * + * @param int $exitCode + * @return boolean true to respawn + */ + protected function shouldRespawn($exitCode) + { + if ($exitCode == self::EXIT_SHUTDOWN) { + // Thread requested a clean shutdown. + return false; + } else { + // Otherwise we should always respawn! + return true; + } + } + /** * Initialize things for a fresh thread, call runThread(), and * exit at completion with appropriate return value. @@ -116,8 +146,8 @@ abstract class SpawningDaemon extends Daemon { $this->set_id($this->get_id() . "." . $thread); $this->resetDb(); - $ok = $this->runThread(); - exit($ok ? 0 : 1); + $exitCode = $this->runThread(); + exit($exitCode); } /** diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index 8f0091a13..89f3d74cc 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -38,8 +38,10 @@ class StompQueueManager extends QueueManager var $password = null; var $base = null; var $con = null; + protected $control; protected $sites = array(); + protected $subscriptions = array(); protected $useTransactions = true; protected $transaction = null; @@ -52,6 +54,7 @@ class StompQueueManager extends QueueManager $this->username = common_config('queue', 'stomp_username'); $this->password = common_config('queue', 'stomp_password'); $this->base = common_config('queue', 'queue_basename'); + $this->control = common_config('queue', 'control_channel'); } /** @@ -77,6 +80,36 @@ class StompQueueManager extends QueueManager $this->initialize(); } + /** + * Optional; ping any running queue handler daemons with a notification + * such as announcing a new site to handle or requesting clean shutdown. + * This avoids having to restart all the daemons manually to update configs + * and such. + * + * Currently only relevant for multi-site queue managers such as Stomp. + * + * @param string $event event key + * @param string $param optional parameter to append to key + * @return boolean success + */ + public function sendControlSignal($event, $param='') + { + $message = $event; + if ($param != '') { + $message .= ':' . $param; + } + $this->_connect(); + $result = $this->con->send($this->control, + $message, + array ('created' => common_sql_now())); + if ($result) { + $this->_log(LOG_INFO, "Sent control ping to queue daemons: $message"); + return true; + } else { + $this->_log(LOG_ERR, "Failed sending control ping to queue daemons: $message"); + return false; + } + } /** * Instantiate the appropriate QueueHandler class for the given queue. @@ -86,7 +119,7 @@ class StompQueueManager extends QueueManager */ function getHandler($queue) { - $handlers = $this->handlers[common_config('site', 'server')]; + $handlers = $this->handlers[$this->currentSite()]; if (isset($handlers[$queue])) { $class = $handlers[$queue]; if (class_exists($class)) { @@ -108,7 +141,7 @@ class StompQueueManager extends QueueManager function getQueues() { $group = $this->activeGroup(); - $site = common_config('site', 'server'); + $site = $this->currentSite(); if (empty($this->groups[$site][$group])) { return array(); } else { @@ -126,8 +159,8 @@ class StompQueueManager extends QueueManager */ public function connect($transport, $class, $group='queuedaemon') { - $this->handlers[common_config('site', 'server')][$transport] = $class; - $this->groups[common_config('site', 'server')][$group][$transport] = $class; + $this->handlers[$this->currentSite()][$transport] = $class; + $this->groups[$this->currentSite()][$group][$transport] = $class; } /** @@ -180,7 +213,16 @@ class StompQueueManager extends QueueManager $ok = true; $frames = $this->con->readFrames(); foreach ($frames as $frame) { - $ok = $ok && $this->_handleItem($frame); + $dest = $frame->headers['destination']; + if ($dest == $this->control) { + if (!$this->handleControlSignal($frame)) { + // We got a control event that requests a shutdown; + // close out and stop handling anything else! + break; + } + } else { + $ok = $ok && $this->handleItem($frame); + } } return $ok; } @@ -197,6 +239,9 @@ class StompQueueManager extends QueueManager public function start($master) { parent::start($master); + $this->_connect(); + + $this->con->subscribe($this->control); if ($this->sites) { foreach ($this->sites as $server) { StatusNet::init($server); @@ -221,6 +266,7 @@ class StompQueueManager extends QueueManager // If there are any outstanding delivered messages we haven't processed, // free them for another thread to take. $this->rollback(); + $this->con->unsubscribe($this->control); if ($this->sites) { foreach ($this->sites as $server) { StatusNet::init($server); @@ -231,7 +277,16 @@ class StompQueueManager extends QueueManager } return true; } - + + /** + * Get identifier of the currently active site configuration + * @return string + */ + protected function currentSite() + { + return common_config('site', 'server'); // @fixme switch to nickname + } + /** * Lazy open connection to Stomp queue server. */ @@ -255,22 +310,29 @@ class StompQueueManager extends QueueManager */ protected function doSubscribe() { + $site = $this->currentSite(); $this->_connect(); foreach ($this->getQueues() as $queue) { $rawqueue = $this->queueName($queue); + $this->subscriptions[$site][$queue] = $rawqueue; $this->_log(LOG_INFO, "Subscribing to $rawqueue"); $this->con->subscribe($rawqueue); } } - + /** * Subscribe from all enabled notice queues for the current site. */ protected function doUnsubscribe() { + $site = $this->currentSite(); $this->_connect(); - foreach ($this->getQueues() as $queue) { - $this->con->unsubscribe($this->queueName($queue)); + if (!empty($this->subscriptions[$site])) { + foreach ($this->subscriptions[$site] as $queue => $rawqueue) { + $this->_log(LOG_INFO, "Unsubscribing from $rawqueue"); + $this->con->unsubscribe($rawqueue); + unset($this->subscriptions[$site][$queue]); + } } } @@ -286,10 +348,10 @@ class StompQueueManager extends QueueManager * @param StompFrame $frame * @return bool */ - protected function _handleItem($frame) + protected function handleItem($frame) { list($site, $queue) = $this->parseDestination($frame->headers['destination']); - if ($site != common_config('site', 'server')) { + if ($site != $this->currentSite()) { $this->stats('switch'); StatusNet::init($site); } @@ -317,7 +379,7 @@ class StompQueueManager extends QueueManager $handler = $this->getHandler($queue); if (!$handler) { - $this->_log(LOG_ERROR, "Missing handler class; skipping $info"); + $this->_log(LOG_ERR, "Missing handler class; skipping $info"); $this->ack($frame); $this->commit(); $this->begin(); @@ -348,6 +410,77 @@ class StompQueueManager extends QueueManager return true; } + /** + * Process a control signal broadcast. + * + * @param array $frame Stomp frame + * @return bool true to continue; false to stop further processing. + */ + protected function handleControlSignal($frame) + { + $message = trim($frame->body); + if (strpos($message, ':') !== false) { + list($event, $param) = explode(':', $message, 2); + } else { + $event = $message; + $param = ''; + } + + $shutdown = false; + + if ($event == 'shutdown') { + $this->master->requestShutdown(); + $shutdown = true; + } else if ($event == 'restart') { + $this->master->requestRestart(); + $shutdown = true; + } else if ($event == 'update') { + $this->updateSiteConfig($param); + } else { + $this->_log(LOG_ERR, "Ignoring unrecognized control message: $message"); + } + + $this->ack($frame); + $this->commit(); + $this->begin(); + return $shutdown; + } + + /** + * Set us up with queue subscriptions for a new site added at runtime, + * triggered by a broadcast to the 'statusnet-control' topic. + * + * @param array $frame Stomp frame + * @return bool true to continue; false to stop further processing. + */ + protected function updateSiteConfig($nickname) + { + if (empty($this->sites)) { + if ($nickname == common_config('site', 'nickname')) { + StatusNet::init(common_config('site', 'server')); + $this->doUnsubscribe(); + $this->doSubscribe(); + } else { + $this->_log(LOG_INFO, "Ignoring update ping for other site $nickname"); + } + } else { + $sn = Status_network::staticGet($nickname); + if ($sn) { + $server = $sn->getServerName(); // @fixme do config-by-nick + StatusNet::init($server); + if (empty($this->sites[$server])) { + $this->addSite($server); + } + $this->_log(LOG_INFO, "(Re)subscribing to queues for site $nickname / $server"); + $this->doUnsubscribe(); + $this->doSubscribe(); + $this->stats('siteupdate'); + } else { + $this->_log(LOG_ERR, "Ignoring ping for unrecognized new site $nickname"); + } + } + } + /** * Combines the queue_basename from configuration with the * site server name and queue name to give eg: @@ -360,7 +493,7 @@ class StompQueueManager extends QueueManager protected function queueName($queue) { return common_config('queue', 'queue_basename') . - common_config('site', 'server') . '/' . $queue; + $this->currentSite() . '/' . $queue; } /** diff --git a/scripts/queuectl.php b/scripts/queuectl.php new file mode 100755 index 000000000..1c9ea3353 --- /dev/null +++ b/scripts/queuectl.php @@ -0,0 +1,85 @@ +#!/usr/bin/env php +. + */ + +/** + * Sends control signals to running queue daemons. + * + * @author Brion Vibber + * @package QueueHandler + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'ur'; +$longoptions = array('update', 'restart', 'stop'); + +$helptext = <<sendControlSignal($event, $param)) { + print " sent.\n"; + } else { + print " FAILED.\n"; + } +} + +$actions = 0; + +if (have_option('u') || have_option('--update')) { + $nickname = common_config('site', 'nickname'); + doSendControl("Sending site update signal to queue daemons for $nickname", + "update", $nickname); + $actions++; +} + +if (have_option('r') || have_option('--restart')) { + doSendControl("Sending graceful restart signal to queue daemons...", + "restart"); + $actions++; +} + +if (have_option('--stop')) { + doSendControl("Sending graceful shutdown signal to queue daemons...", + "shutdown"); + $actions++; +} + +if (!$actions) { + show_help(); +} + diff --git a/scripts/queuedaemon.php b/scripts/queuedaemon.php index bedd14b1a..c2e2351c3 100755 --- a/scripts/queuedaemon.php +++ b/scripts/queuedaemon.php @@ -115,7 +115,7 @@ class QueueDaemon extends SpawningDaemon $this->log(LOG_INFO, 'terminating normally'); - return true; + return $master->respawn ? self::EXIT_RESTART : self::EXIT_SHUTDOWN; } } diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php index fd7cf055b..46dd9b90c 100755 --- a/scripts/xmppdaemon.php +++ b/scripts/xmppdaemon.php @@ -56,7 +56,7 @@ class XMPPDaemon extends SpawningDaemon common_log(LOG_INFO, 'terminating normally'); - return true; + return $master->respawn ? self::EXIT_RESTART : self::EXIT_SHUTDOWN; } } -- cgit v1.2.3-54-g00ecf From 03685bba1ea6c1b8f2c3db8d2f095a221b29a464 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 15:11:09 -0800 Subject: - Remove redudant/unused 'server' setting from site admin panel - Move 'fancy urls' checkbox from site admin panel to paths admin panel --- actions/pathsadminpanel.php | 33 ++++++++++++++++++++++++++++++--- actions/siteadminpanel.php | 21 +++------------------ 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/actions/pathsadminpanel.php b/actions/pathsadminpanel.php index 3779fcfaa..9155a7e42 100644 --- a/actions/pathsadminpanel.php +++ b/actions/pathsadminpanel.php @@ -24,7 +24,7 @@ * @author Evan Prodromou * @author Zach Copley * @author Sarven Capadisli - * @copyright 2008-2009 StatusNet, Inc. + * @copyright 2008-2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -98,6 +98,11 @@ class PathsadminpanelAction extends AdminPanelAction 'background' => array('server', 'dir', 'path') ); + // XXX: If we're only going to have one boolean on thi page we + // can remove some of the boolean processing code --Z + + static $booleans = array('site' => array('fancy')); + $values = array(); foreach ($settings as $section => $parts) { @@ -106,6 +111,12 @@ class PathsadminpanelAction extends AdminPanelAction } } + foreach ($booleans as $section => $parts) { + foreach ($parts as $setting) { + $values[$section][$setting] = ($this->boolean($setting)) ? 1 : 0; + } + } + $this->validate($values); // assert(all values are valid); @@ -120,7 +131,13 @@ class PathsadminpanelAction extends AdminPanelAction } } - $config->query('COMMIT'); + foreach ($booleans as $section => $parts) { + foreach ($parts as $setting) { + Config::save($section, $setting, $values[$section][$setting]); + } + } + + $config->query('COMMIT'); return; } @@ -213,10 +230,14 @@ class PathsAdminPanelForm extends AdminForm function formData() { - $this->out->elementStart('fieldset', array('id' => 'settings_paths_locale')); + $this->out->elementStart('fieldset', array('id' => 'settings_paths_locale')); $this->out->element('legend', null, _('Site'), 'site'); $this->out->elementStart('ul', 'form_data'); + $this->li(); + $this->input('server', _('Server'), _('Site\'s server hostname.')); + $this->unli(); + $this->li(); $this->input('path', _('Path'), _('Site path')); $this->unli(); @@ -225,6 +246,12 @@ class PathsAdminPanelForm extends AdminForm $this->input('locale_path', _('Path to locales'), _('Directory path to locales'), 'site'); $this->unli(); + $this->li(); + $this->out->checkbox('fancy', _('Fancy URLs'), + (bool) $this->value('fancy'), + _('Use fancy (more readable and memorable) URLs?')); + $this->unli(); + $this->out->elementEnd('ul'); $this->out->elementEnd('fieldset'); diff --git a/actions/siteadminpanel.php b/actions/siteadminpanel.php index dd388a18a..1cb57ec09 100644 --- a/actions/siteadminpanel.php +++ b/actions/siteadminpanel.php @@ -24,7 +24,7 @@ * @author Evan Prodromou * @author Zach Copley * @author Sarven Capadisli - * @copyright 2008-2009 StatusNet, Inc. + * @copyright 2008-2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -95,7 +95,7 @@ class SiteadminpanelAction extends AdminPanelAction 'site', 'textlimit', 'dupelimit'), 'snapshot' => array('run', 'reporturl', 'frequency')); - static $booleans = array('site' => array('private', 'inviteonly', 'closed', 'fancy')); + static $booleans = array('site' => array('private', 'inviteonly', 'closed')); $values = array(); @@ -299,22 +299,7 @@ class SiteAdminPanelForm extends AdminForm $this->out->elementEnd('ul'); $this->out->elementEnd('fieldset'); - $this->out->elementStart('fieldset', array('id' => 'settings_admin_urls')); - $this->out->element('legend', null, _('URLs')); - $this->out->elementStart('ul', 'form_data'); - $this->li(); - $this->input('server', _('Server'), _('Site\'s server hostname.')); - $this->unli(); - - $this->li(); - $this->out->checkbox('fancy', _('Fancy URLs'), - (bool) $this->value('fancy'), - _('Use fancy (more readable and memorable) URLs?')); - $this->unli(); - $this->out->elementEnd('ul'); - $this->out->elementEnd('fieldset'); - - $this->out->elementStart('fieldset', array('id' => 'settings_admin_access')); + $this->out->elementStart('fieldset', array('id' => 'settings_admin_access')); $this->out->element('legend', null, _('Access')); $this->out->elementStart('ul', 'form_data'); $this->li(); -- cgit v1.2.3-54-g00ecf From aad42427ccd4d3824efcbce43d8e9dba6d5b475d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 15:56:19 -0800 Subject: New access admin panel for site registration settings --- actions/accessadminpanel.php | 192 +++++++++++++++++++++++++++++++++++++++++++ actions/siteadminpanel.php | 37 --------- lib/adminpanelaction.php | 13 ++- lib/default.php | 2 +- lib/router.php | 1 + 5 files changed, 203 insertions(+), 42 deletions(-) create mode 100644 actions/accessadminpanel.php diff --git a/actions/accessadminpanel.php b/actions/accessadminpanel.php new file mode 100644 index 000000000..4768e2faf --- /dev/null +++ b/actions/accessadminpanel.php @@ -0,0 +1,192 @@ +. + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Administer site access settings + * + * @category Admin + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class AccessadminpanelAction extends AdminPanelAction +{ + /** + * Returns the page title + * + * @return string page title + */ + + function title() + { + return _('Access'); + } + + /** + * Instructions for using this form. + * + * @return string instructions + */ + + function getInstructions() + { + return _('Site access settings'); + } + + /** + * Show the site admin panel form + * + * @return void + */ + + function showForm() + { + $form = new AccessAdminPanelForm($this); + $form->show(); + return; + } + + /** + * Save settings from the form + * + * @return void + */ + + function saveSettings() + { + static $booleans = array('site' => array('private', 'inviteonly', 'closed')); + + foreach ($booleans as $section => $parts) { + foreach ($parts as $setting) { + $values[$section][$setting] = ($this->boolean($setting)) ? 1 : 0; + } + } + + $config = new Config(); + + $config->query('BEGIN'); + + foreach ($booleans as $section => $parts) { + foreach ($parts as $setting) { + Config::save($section, $setting, $values[$section][$setting]); + } + } + + $config->query('COMMIT'); + + return; + } + +} + +class AccessAdminPanelForm extends AdminForm +{ + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'form_site_admin_panel'; + } + + /** + * class of the form + * + * @return string class of the form + */ + + function formClass() + { + return 'form_settings'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('accessadminpanel'); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $this->out->elementStart('fieldset', array('id' => 'settings_admin_access')); + $this->out->element('legend', null, _('Registration')); + $this->out->elementStart('ul', 'form_data'); + $this->li(); + $this->out->checkbox('private', _('Private'), + (bool) $this->value('private'), + _('Prohibit anonymous users (not logged in) from viewing site?')); + $this->unli(); + + $this->li(); + $this->out->checkbox('inviteonly', _('Invite only'), + (bool) $this->value('inviteonly'), + _('Make registration invitation only.')); + $this->unli(); + + $this->li(); + $this->out->checkbox('closed', _('Closed'), + (bool) $this->value('closed'), + _('Disable new registrations.')); + $this->unli(); + $this->out->elementEnd('ul'); + $this->out->elementEnd('fieldset'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Save'), 'submit', null, _('Save access settings')); + } + +} diff --git a/actions/siteadminpanel.php b/actions/siteadminpanel.php index 1cb57ec09..8c8f8b374 100644 --- a/actions/siteadminpanel.php +++ b/actions/siteadminpanel.php @@ -95,8 +95,6 @@ class SiteadminpanelAction extends AdminPanelAction 'site', 'textlimit', 'dupelimit'), 'snapshot' => array('run', 'reporturl', 'frequency')); - static $booleans = array('site' => array('private', 'inviteonly', 'closed')); - $values = array(); foreach ($settings as $section => $parts) { @@ -105,12 +103,6 @@ class SiteadminpanelAction extends AdminPanelAction } } - foreach ($booleans as $section => $parts) { - foreach ($parts as $setting) { - $values[$section][$setting] = ($this->boolean($setting)) ? 1 : 0; - } - } - // This throws an exception on validation errors $this->validate($values); @@ -127,12 +119,6 @@ class SiteadminpanelAction extends AdminPanelAction } } - foreach ($booleans as $section => $parts) { - foreach ($parts as $setting) { - Config::save($section, $setting, $values[$section][$setting]); - } - } - $config->query('COMMIT'); return; @@ -299,29 +285,6 @@ class SiteAdminPanelForm extends AdminForm $this->out->elementEnd('ul'); $this->out->elementEnd('fieldset'); - $this->out->elementStart('fieldset', array('id' => 'settings_admin_access')); - $this->out->element('legend', null, _('Access')); - $this->out->elementStart('ul', 'form_data'); - $this->li(); - $this->out->checkbox('private', _('Private'), - (bool) $this->value('private'), - _('Prohibit anonymous users (not logged in) from viewing site?')); - $this->unli(); - - $this->li(); - $this->out->checkbox('inviteonly', _('Invite only'), - (bool) $this->value('inviteonly'), - _('Make registration invitation only.')); - $this->unli(); - - $this->li(); - $this->out->checkbox('closed', _('Closed'), - (bool) $this->value('closed'), - _('Disable new registrations.')); - $this->unli(); - $this->out->elementEnd('ul'); - $this->out->elementEnd('fieldset'); - $this->out->elementStart('fieldset', array('id' => 'settings_admin_snapshots')); $this->out->element('legend', null, _('Snapshots')); $this->out->elementStart('ul', 'form_data'); diff --git a/lib/adminpanelaction.php b/lib/adminpanelaction.php index a6981ac61..f62bfa458 100644 --- a/lib/adminpanelaction.php +++ b/lib/adminpanelaction.php @@ -319,12 +319,17 @@ class AdminPanelNav extends Widget if ($this->canAdmin('user')) { $this->out->menuItem(common_local_url('useradminpanel'), _('User'), - _('Paths configuration'), $action_name == 'useradminpanel', 'nav_design_admin_panel'); + _('User configuration'), $action_name == 'useradminpanel', 'nav_design_admin_panel'); } - if ($this->canAdmin('paths')) { - $this->out->menuItem(common_local_url('pathsadminpanel'), _('Paths'), - _('Paths configuration'), $action_name == 'pathsadminpanel', 'nav_design_admin_panel'); + if ($this->canAdmin('access')) { + $this->out->menuItem(common_local_url('accessadminpanel'), _('Access'), + _('Access configuration'), $action_name == 'accessadminpanel', 'nav_design_admin_panel'); + } + + if ($this->canAdmin('paths')) { + $this->out->menuItem(common_local_url('pathsadminpanel'), _('Paths'), + _('Paths configuration'), $action_name == 'pathsadminpanel', 'nav_design_admin_panel'); } Event::handle('EndAdminPanelNav', array($this)); diff --git a/lib/default.php b/lib/default.php index d2dd8ab33..37fb65b53 100644 --- a/lib/default.php +++ b/lib/default.php @@ -260,7 +260,7 @@ $default = 'OpenID' => null), ), 'admin' => - array('panels' => array('design', 'site', 'user', 'paths')), + array('panels' => array('design', 'site', 'user', 'paths', 'access')), 'singleuser' => array('enabled' => false, 'nickname' => null), diff --git a/lib/router.php b/lib/router.php index c0ddc8db3..03765b39d 100644 --- a/lib/router.php +++ b/lib/router.php @@ -637,6 +637,7 @@ class Router $m->connect('admin/site', array('action' => 'siteadminpanel')); $m->connect('admin/design', array('action' => 'designadminpanel')); $m->connect('admin/user', array('action' => 'useradminpanel')); + $m->connect('admin/access', array('action' => 'accessadminpanel')); $m->connect('admin/paths', array('action' => 'pathsadminpanel')); $m->connect('getfile/:filename', -- cgit v1.2.3-54-g00ecf From 492950b784ccf76ad96a24732c4329e3b1ca5ac8 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Thu, 14 Jan 2010 23:36:13 +0100 Subject: Fix inconsistent title case in page title --- actions/oauthconnectionssettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index b17729b82..c2e8d441b 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -68,7 +68,7 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction function title() { - return _('Connected Applications'); + return _('Connected applications'); } function isReadOnly($args) -- cgit v1.2.3-54-g00ecf From 97e1acdc329c3f4dbabce37ff5985f655aa91541 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Thu, 14 Jan 2010 23:37:06 +0100 Subject: Fix casing for HMAC-SHA1. --- actions/showapplication.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/actions/showapplication.php b/actions/showapplication.php index 049206375..a6ff425c7 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -265,7 +265,7 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementEnd('dl'); $this->element('p', 'note', - _('Note: We support hmac-sha1 signatures. We do not support the plaintext signature method.')); + _('Note: We support HMAC-SHA1 signatures. We do not support the plaintext signature method.')); $this->elementEnd('div'); $this->elementStart('p', array('id' => 'application_action')); @@ -325,4 +325,3 @@ class ShowApplicationAction extends OwnerDesignAction } } - -- cgit v1.2.3-54-g00ecf From 4202ffff9184eaf0934184309ef8f3b9a90d60d9 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Thu, 14 Jan 2010 23:38:29 +0100 Subject: Make more complete sentence. --- lib/applicationeditform.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index 040d3bf74..6f03a9bed 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -203,7 +203,7 @@ class ApplicationEditForm extends Form $maxDesc = Oauth_application::maxDesc(); if ($maxDesc > 0) { - $descInstr = sprintf(_('Describe your application in %d chars'), + $descInstr = sprintf(_('Describe your application in %d characters'), $maxDesc); } else { $descInstr = _('Describe your application'); -- cgit v1.2.3-54-g00ecf From 923b7de3c661098cfe3d5bdc0878d9329e1ee2bf Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 27 Jan 2010 08:41:26 +0000 Subject: - Check for read-only vs. read-write access to protected API resources (OAuth) - Some cleanup --- actions/apistatusesupdate.php | 18 +++--- lib/apiauth.php | 124 ++++++++++++++++++++++-------------------- 2 files changed, 74 insertions(+), 68 deletions(-) diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index 31c9b20ce..bf367e1e1 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -28,7 +28,7 @@ * @author Mike Cochrane * @author Robin Millette * @author Zach Copley - * @copyright 2009 StatusNet, Inc. + * @copyright 2009-2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -79,7 +79,6 @@ class ApiStatusesUpdateAction extends ApiAuthAction { parent::prepare($args); - $this->user = $this->auth_user; $this->status = $this->trimmed('status'); $this->source = $this->trimmed('source'); $this->lat = $this->trimmed('lat'); @@ -145,7 +144,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction return; } - if (empty($this->user)) { + if (empty($this->auth_user)) { $this->clientError(_('No such user.'), 404, $this->format); return; } @@ -172,7 +171,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction // Check for commands $inter = new CommandInterpreter(); - $cmd = $inter->handle_command($this->user, $status_shortened); + $cmd = $inter->handle_command($this->auth_user, $status_shortened); if ($cmd) { @@ -184,7 +183,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction // And, it returns your last status whether the cmd was successful // or not! - $this->notice = $this->user->getCurrentNotice(); + $this->notice = $this->auth_user->getCurrentNotice(); } else { @@ -211,7 +210,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction $upload = null; try { - $upload = MediaFile::fromUpload('media', $this->user); + $upload = MediaFile::fromUpload('media', $this->auth_user); } catch (ClientException $ce) { $this->clientError($ce->getMessage()); return; @@ -234,19 +233,19 @@ class ApiStatusesUpdateAction extends ApiAuthAction $options = array('reply_to' => $reply_to); - if ($this->user->shareLocation()) { + if ($this->auth_user->shareLocation()) { $locOptions = Notice::locationOptions($this->lat, $this->lon, null, null, - $this->user->getProfile()); + $this->auth_user->getProfile()); $options = array_merge($options, $locOptions); } $this->notice = - Notice::saveNew($this->user->id, + Notice::saveNew($this->auth_user->id, $content, $this->source, $options); @@ -255,7 +254,6 @@ class ApiStatusesUpdateAction extends ApiAuthAction $upload->attachToNotice($this->notice); } - } $this->showNotice(); diff --git a/lib/apiauth.php b/lib/apiauth.php index 37070d212..ad9651ff2 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -29,7 +29,7 @@ * @author mEDI * @author Sarven Capadisli * @author Zach Copley - * @copyright 2009 StatusNet, Inc. + * @copyright 2009-2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -53,9 +53,11 @@ require_once INSTALLDIR . '/lib/apioauth.php'; class ApiAuthAction extends ApiAction { - var $access_token; - var $oauth_access_type; - var $oauth_source; + var $auth_user_nickname = null; + var $auth_user_password = null; + var $access_token = null; + var $oauth_source = null; + var $auth_user = null; /** * Take arguments for running, and output basic auth header if needed @@ -70,18 +72,28 @@ class ApiAuthAction extends ApiAction { parent::prepare($args); - if ($this->requiresAuth()) { + $this->consumer_key = $this->arg('oauth_consumer_key'); + $this->access_token = $this->arg('oauth_token'); - $this->consumer_key = $this->arg('oauth_consumer_key'); - $this->access_token = $this->arg('oauth_token'); + // NOTE: $this->auth_user has to get set in prepare(), not handle(), + // because subclasses do stuff with it in their prepares. + if ($this->requiresAuth()) { if (!empty($this->access_token)) { $this->checkOAuthRequest(); } else { $this->checkBasicAuthUser(); - // By default, all basic auth users have read and write access + } + + // Reject API calls with the wrong access level - $this->access = self::READ_WRITE; + if ($this->isReadOnly($args) == false) { + if ($this->access != self::READ_WRITE) { + $msg = 'API resource requires read-write access, ' . + 'but you only have read access.'; + $this->clientError($msg, 401, $this->format); + exit(); + } } } @@ -95,8 +107,6 @@ class ApiAuthAction extends ApiAction function checkOAuthRequest() { - common_debug("We have an OAuth request."); - $datastore = new ApiStatusNetOAuthDataStore(); $server = new OAuthServer($datastore); $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); @@ -114,9 +124,10 @@ class ApiAuthAction extends ApiAction if (empty($app)) { - // this should really not happen - common_log(LOG_WARN, - "Couldn't find the OAuth app for consumer key: $this->consumer_key"); + // this should probably not happen + common_log(LOG_WARNING, + 'Couldn\'t find the OAuth app for consumer key: ' . + $this->consumer_key); throw new OAuthException('No application for that consumer key.'); } @@ -128,20 +139,18 @@ class ApiAuthAction extends ApiAction $appUser = Oauth_application_user::staticGet('token', $this->access_token); - // XXX: check that app->id and appUser->application_id and consumer all + // XXX: Check that app->id and appUser->application_id and consumer all // match? if (!empty($appUser)) { - // read or read-write - $this->oauth_access_type = $appUser->access_type; - // If access_type == 0 we have either a request token // or a bad / revoked access token - if ($this->oauth_access_type != 0) { + if ($appUser->access_type != 0) { + + // Set the access level for the api call - // Set the read or read-write access for the api call $this->access = ($appUser->access_type & Oauth_application::$writeAccess) ? self::READ_WRITE : self::READ_ONLY; @@ -151,38 +160,34 @@ class ApiAuthAction extends ApiAction } $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . - "application '%s' (id: %d)."; + "application '%s' (id: %d) with %s access."; common_log(LOG_INFO, sprintf($msg, $this->auth_user->nickname, $this->auth_user->id, $app->name, - $app->id)); + $app->id, + ($this->access = self::READ_WRITE) ? + 'read-write' : 'read-only' + )); return true; } else { throw new OAuthException('Bad access token.'); } } else { - // also should not happen + // Also should not happen + throw new OAuthException('No user for that token.'); - } + } } catch (OAuthException $e) { - common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); - common_debug(var_export($req, true)); - $this->showOAuthError($e->getMessage()); - exit(); + common_log(LOG_WARNING, 'API OAuthException - ' . $e->getMessage()); + $this->showAuthError(); + exit; } } - function showOAuthError($msg) - { - header('HTTP/1.1 401 Unauthorized'); - header('Content-Type: text/html; charset=utf-8'); - print $msg . "\n"; - } - /** * Does this API resource require authentication? * @@ -207,39 +212,44 @@ class ApiAuthAction extends ApiAction $realm = common_config('site', 'name') . ' API'; - if (!isset($this->auth_user)) { + if (!isset($this->auth_user_nickname)) { header('WWW-Authenticate: Basic realm="' . $realm . '"'); // show error if the user clicks 'cancel' - $this->showBasicAuthError(); + $this->showAuthError(); exit; } else { - $nickname = $this->auth_user; - $password = $this->auth_pw; - $user = common_check_user($nickname, $password); + + $user = common_check_user($this->auth_user_nickname, + $this->auth_user_password); + if (Event::handle('StartSetApiUser', array(&$user))) { $this->auth_user = $user; Event::handle('EndSetApiUser', array($user)); } + // By default, basic auth users have rw access + + $this->access = self::READ_WRITE; + if (empty($this->auth_user)) { // basic authentication failed list($proxy, $ip) = common_client_ip(); + common_log( LOG_WARNING, 'Failed API auth attempt, nickname = ' . "$nickname, proxy = $proxy, ip = $ip." ); - $this->showBasicAuthError(); + + $this->showAuthError(); exit; } } - - return true; } /** @@ -253,32 +263,30 @@ class ApiAuthAction extends ApiAction { if (isset($_SERVER['AUTHORIZATION']) || isset($_SERVER['HTTP_AUTHORIZATION']) - ) { - $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION']) - ? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION']; + ) { + $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION']) + ? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION']; } if (isset($_SERVER['PHP_AUTH_USER'])) { - $this->auth_user = $_SERVER['PHP_AUTH_USER']; - $this->auth_pw = $_SERVER['PHP_AUTH_PW']; + $this->auth_user_nickname = $_SERVER['PHP_AUTH_USER']; + $this->auth_user_password = $_SERVER['PHP_AUTH_PW']; } elseif (isset($authorization_header) && strstr(substr($authorization_header, 0, 5), 'Basic')) { - // decode the HTTP_AUTHORIZATION header on php-cgi server self + // Decode the HTTP_AUTHORIZATION header on php-cgi server self // on fcgid server the header name is AUTHORIZATION $auth_hash = base64_decode(substr($authorization_header, 6)); - list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash); + list($this->auth_user_nickname, + $this->auth_user_password) = explode(':', $auth_hash); - // set all to null on a empty basic auth request + // Set all to null on a empty basic auth request - if ($this->auth_user == "") { - $this->auth_user = null; - $this->auth_pw = null; + if (empty($this->auth_user_nickname)) { + $this->auth_user_nickname = null; + $this->auth_password = null; } - } else { - $this->auth_user = null; - $this->auth_pw = null; } } @@ -289,7 +297,7 @@ class ApiAuthAction extends ApiAction * @return void */ - function showBasicAuthError() + function showAuthError() { header('HTTP/1.1 401 Unauthorized'); $msg = 'Could not authenticate you.'; -- cgit v1.2.3-54-g00ecf From 756da7bc5174f58f714c858f0100f04b3561a250 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 27 Jan 2010 08:45:56 +0000 Subject: s/LOG_WARN/LOG_WARNING/ --- actions/apioauthaccesstoken.php | 2 +- actions/apioauthrequesttoken.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php index 085ef6f0b..887df4c20 100644 --- a/actions/apioauthaccesstoken.php +++ b/actions/apioauthaccesstoken.php @@ -70,7 +70,7 @@ class ApiOauthAccessTokenAction extends ApiOauthAction $atok = $server->fetch_access_token($req); } catch (OAuthException $e) { - common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); + common_log(LOG_WARNING, 'API OAuthException - ' . $e->getMessage()); common_debug(var_export($req, true)); $this->outputError($e->getMessage()); return; diff --git a/actions/apioauthrequesttoken.php b/actions/apioauthrequesttoken.php index 467640b9a..4fa626d86 100644 --- a/actions/apioauthrequesttoken.php +++ b/actions/apioauthrequesttoken.php @@ -89,7 +89,7 @@ class ApiOauthRequestTokenAction extends ApiOauthAction $token = $server->fetch_request_token($req); print $token; } catch (OAuthException $e) { - common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); + common_log(LOG_WARNING, 'API OAuthException - ' . $e->getMessage()); header('HTTP/1.1 401 Unauthorized'); header('Content-Type: text/html; charset=utf-8'); print $e->getMessage() . "\n"; -- cgit v1.2.3-54-g00ecf From 54d04a0c911269036a1fc3708fec6d70c33d5032 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 27 Jan 2010 09:59:40 +0000 Subject: Test script to update your status via OAuth --- tests/oauth/statusupdate.php | 115 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tests/oauth/statusupdate.php diff --git a/tests/oauth/statusupdate.php b/tests/oauth/statusupdate.php new file mode 100644 index 000000000..4aa230e28 --- /dev/null +++ b/tests/oauth/statusupdate.php @@ -0,0 +1,115 @@ +#!/usr/bin/env php +. + **/ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); + +require_once INSTALLDIR . '/extlib/OAuth.php'; + +$shortoptions = 'o:s:u:'; +$longoptions = array('oauth_token=', 'token_secret=', 'update='); + +$helptext = <<sign_request($hmac_method, $test_consumer, $at); + +$r = httpRequest($req_req->to_url()); + +$body = $r->getBody(); + +print "$body\n"; + +//print $req_req->to_url() . "\n\n"; + +function httpRequest($url) +{ + $request = HTTPClient::start(); + + $request->setConfig(array( + 'follow_redirects' => true, + 'connect_timeout' => 120, + 'timeout' => 120, + 'ssl_verify_peer' => false, + 'ssl_verify_host' => false + )); + + return $request->post($url); +} + -- cgit v1.2.3-54-g00ecf From 04a37fa1c703955a51862033d83e59836c35d74f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 27 Jan 2010 15:08:33 +0000 Subject: Better alignment for notice in shownotice page --- theme/base/css/display.css | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 84e9426c7..65dd15990 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1039,12 +1039,13 @@ overflow:visible; #showstream .notice div.entry-content { margin-left:0; } -#shownotice .notice .entry-title, -#shownotice .notice div.entry-content { -margin-left:110px; -} #shownotice .notice .entry-title { +margin-left:110px; font-size:2.2em; +min-height:123px; +} +#shownotice .notice div.entry-content { +margin-left:0; } .notice p.entry-content { -- cgit v1.2.3-54-g00ecf From c52951cef5508452733f84dec7815daf4aca1016 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 11:37:22 -0500 Subject: Optionally set a separate Javascript server and path We have about 10-12 JavaScript pages per Web page. They usually are based on the same server as the Web pages, but since they're static files, it makes sense to offload them to a lite server that handles static files well. This commit lets you set a separate Javascript server and path for the default Javascript code in StatusNet. Squashed commit of the following: commit 139d1622fdafe5ad00c820224416d9021efc3234 Author: Evan Prodromou Date: Wed Jan 27 11:30:24 2010 -0500 modules that call htmloutputter::script() don't prescribe js/ path commit c6ca3174af73efed55eaed5ff1e2a3bdc77d2d87 Author: Evan Prodromou Date: Wed Jan 27 11:28:07 2010 -0500 configurable server and path for javascript files --- actions/avatarsettings.php | 4 ++-- actions/designadminpanel.php | 4 ++-- actions/grouplogo.php | 4 ++-- lib/action.php | 16 ++++++++-------- lib/default.php | 3 +++ lib/designsettings.php | 4 ++-- lib/htmloutputter.php | 28 +++++++++++++++++++++++++++- plugins/Facebook/facebookaction.php | 2 +- 8 files changed, 47 insertions(+), 18 deletions(-) diff --git a/actions/avatarsettings.php b/actions/avatarsettings.php index cf4525552..6a7398746 100644 --- a/actions/avatarsettings.php +++ b/actions/avatarsettings.php @@ -416,8 +416,8 @@ class AvatarsettingsAction extends AccountSettingsAction parent::showScripts(); if ($this->mode == 'crop') { - $this->script('js/jcrop/jquery.Jcrop.min.js'); - $this->script('js/jcrop/jquery.Jcrop.go.js'); + $this->script('jcrop/jquery.Jcrop.min.js'); + $this->script('jcrop/jquery.Jcrop.go.js'); } $this->autofocus('avatarfile'); diff --git a/actions/designadminpanel.php b/actions/designadminpanel.php index 72ad6ade2..30e8bde1a 100644 --- a/actions/designadminpanel.php +++ b/actions/designadminpanel.php @@ -302,8 +302,8 @@ class DesignadminpanelAction extends AdminPanelAction { parent::showScripts(); - $this->script('js/farbtastic/farbtastic.js'); - $this->script('js/userdesign.go.js'); + $this->script('farbtastic/farbtastic.js'); + $this->script('userdesign.go.js'); $this->autofocus('design_background-image_file'); } diff --git a/actions/grouplogo.php b/actions/grouplogo.php index f197aef33..3c9b56296 100644 --- a/actions/grouplogo.php +++ b/actions/grouplogo.php @@ -437,8 +437,8 @@ class GrouplogoAction extends GroupDesignAction parent::showScripts(); if ($this->mode == 'crop') { - $this->script('js/jcrop/jquery.Jcrop.min.js'); - $this->script('js/jcrop/jquery.Jcrop.go.js'); + $this->script('jcrop/jquery.Jcrop.min.js'); + $this->script('jcrop/jquery.Jcrop.go.js'); } $this->autofocus('avatarfile'); diff --git a/lib/action.php b/lib/action.php index 3ffc452de..cc4f4aad0 100644 --- a/lib/action.php +++ b/lib/action.php @@ -246,18 +246,18 @@ class Action extends HTMLOutputter // lawsuit { if (Event::handle('StartShowScripts', array($this))) { if (Event::handle('StartShowJQueryScripts', array($this))) { - $this->script('js/jquery.min.js'); - $this->script('js/jquery.form.js'); - $this->script('js/jquery.cookie.js'); - $this->script('js/json2.js'); - $this->script('js/jquery.joverlay.min.js'); + $this->script('jquery.min.js'); + $this->script('jquery.form.js'); + $this->script('jquery.cookie.js'); + $this->script('json2.js'); + $this->script('jquery.joverlay.min.js'); Event::handle('EndShowJQueryScripts', array($this)); } if (Event::handle('StartShowStatusNetScripts', array($this)) && Event::handle('StartShowLaconicaScripts', array($this))) { - $this->script('js/xbImportNode.js'); - $this->script('js/util.js'); - $this->script('js/geometa.js'); + $this->script('xbImportNode.js'); + $this->script('util.js'); + $this->script('geometa.js'); // Frame-busting code to avoid clickjacking attacks. $this->element('script', array('type' => 'text/javascript'), 'if (window.top !== window.self) { window.top.location.href = window.self.location.href; }'); diff --git a/lib/default.php b/lib/default.php index 37fb65b53..10ea34864 100644 --- a/lib/default.php +++ b/lib/default.php @@ -120,6 +120,9 @@ $default = array('server' => null, 'dir' => null, 'path'=> null), + 'javascript' => + array('server' => null, + 'path'=> null), 'throttle' => array('enabled' => false, // whether to throttle edits; false by default 'count' => 20, // number of allowed messages in timespan diff --git a/lib/designsettings.php b/lib/designsettings.php index 8e44c03a9..4955e9219 100644 --- a/lib/designsettings.php +++ b/lib/designsettings.php @@ -327,8 +327,8 @@ class DesignSettingsAction extends AccountSettingsAction { parent::showScripts(); - $this->script('js/farbtastic/farbtastic.js'); - $this->script('js/userdesign.go.js'); + $this->script('farbtastic/farbtastic.js'); + $this->script('userdesign.go.js'); $this->autofocus('design_background-image_file'); } diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 31660ce95..317f5ea61 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -351,14 +351,40 @@ class HTMLOutputter extends XMLOutputter function script($src, $type='text/javascript') { if(Event::handle('StartScriptElement', array($this,&$src,&$type))) { + $url = parse_url($src); + if( empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment'])) { - $src = common_path($src) . '?version=' . STATUSNET_VERSION; + $path = common_config('javascript', 'path'); + + if (empty($path)) { + $path = common_config('site', 'path') . '/js/'; + } + + if ($path[strlen($path)-1] != '/') { + $path .= '/'; + } + + if ($path[0] != '/') { + $path = '/'.$path; + } + + $server = common_config('javascript', 'server'); + + if (empty($server)) { + $server = common_config('site', 'server'); + } + + // XXX: protocol + + $src = 'http://'.$server.$path.$src . '?version=' . STATUSNET_VERSION; } + $this->element('script', array('type' => $type, 'src' => $src), ' '); + Event::handle('EndScriptElement', array($this,$src,$type)); } } diff --git a/plugins/Facebook/facebookaction.php b/plugins/Facebook/facebookaction.php index 815fee094..389e1ea81 100644 --- a/plugins/Facebook/facebookaction.php +++ b/plugins/Facebook/facebookaction.php @@ -89,7 +89,7 @@ class FacebookAction extends Action function showScripts() { - $this->script('js/facebookapp.js'); + $this->script('facebookapp.js'); } /** -- cgit v1.2.3-54-g00ecf From 7aeb03f7277de247a642b613775383480bccc63f Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 08:53:55 -0800 Subject: quick fix: use common_path() on realtime update JS so it works with the new JS path code (will pull from main server for now) --- plugins/Realtime/RealtimePlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 89640f5be..16e28e94d 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -87,7 +87,7 @@ class RealtimePlugin extends Plugin $scripts = $this->_getScripts(); foreach ($scripts as $script) { - $action->script($script); + $action->script(common_path($script)); } $user = common_current_user(); -- cgit v1.2.3-54-g00ecf From c51539804a7f6f63b3f47fd838b9527262440e3d Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 09:24:59 -0800 Subject: Add persistent:true property to Stomp messages so ActiveMQ doesn't decide to discard them even though persistence is enabled on the broker. :) (Thanks Aric!) --- lib/stompqueuemanager.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index 89f3d74cc..19e8c49b5 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -178,7 +178,8 @@ class StompQueueManager extends QueueManager $result = $this->con->send($this->queueName($queue), $msg, // BODY of the message - array ('created' => common_sql_now())); + array ('created' => common_sql_now(), + 'persistent' => 'true')); if (!$result) { common_log(LOG_ERR, "Error sending $rep to $queue queue"); -- cgit v1.2.3-54-g00ecf From 61a7a7b36b4f4b504dde880695dff5113dbe2a48 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 21 Jan 2010 18:18:32 +0100 Subject: Plugin for Universal Ad Package. Outputs four most widely used ad types. --- plugins/UAP/UAPPlugin.php | 176 ++++++++++++++++++++++++++++++++++++++++++++++ plugins/UAP/uap.css | 50 +++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 plugins/UAP/UAPPlugin.php create mode 100644 plugins/UAP/uap.css diff --git a/plugins/UAP/UAPPlugin.php b/plugins/UAP/UAPPlugin.php new file mode 100644 index 000000000..13c101723 --- /dev/null +++ b/plugins/UAP/UAPPlugin.php @@ -0,0 +1,176 @@ +. + * + * @category Action + * @package StatusNet + * @author Sarven Capadisli + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Outputs the following ad types (based on UAP): + * Medium Rectangle 300x250 + * Rectangle 180x150 + * Leaderboard 728x90 + * Wide Skyscraper 160x600 + * + * Any number of ad types can be used. Enable all using example: + * addPlugin('UAP', array( + * 'MediumRectangle' => '', + * 'Rectangle' => '', + * 'Leaderboard' => '', + * 'WideSkyscraper' => '' + * ) + * ); + * + * @category Plugin + * @package StatusNet + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class UAPPlugin extends Plugin +{ + public $MediumRectangle = null; + public $Rectangle = null; + public $Leaderboard = null; + public $WideSkyscraper = null; + + function __construct($uap = array()) + { + $this->uap = $uap; + + parent::__construct(); + } + + function onInitializePlugin() + { + foreach($this->uap as $key => $value) { + switch(strtolower($key)) { + case 'MediumRectangle': default: + $this->MediumRectangle = $value; + break; + case 'Rectangle': + $this->Rectangle = $value; + break; + case 'Leaderboard': + $this->Leaderboard = $value; + break; + case 'WideSkyscraper': + $this->WideSkyscraper = $value; + break; + } + } + } + + function onEndShowStatusNetStyles($action) + { + $action->cssLink(common_path('plugins/UAP/uap.css'), + null, 'screen, projection, tv'); + return true; + } + + //MediumRectangle ad + function onStartShowAside($action) + { + if (!$this->MediumRectangle) { + return true; + } + + $this->showAd($action, array('id' => 'ad_medium-rectangle'), + $this->MediumRectangle); + + return true; + } + +/* + //Rectangle ad + function onEndShowSiteNotice($action) + { + if (!$this->Rectangle) { + return true; + } + + $this->showAd($action, array('id' => 'ad_rectangle'), + $this->Rectangle); + + return true; + } +*/ + + //Leaderboard and Rectangle ad + function onStartShowHeader($action) + { + if ($this->Leaderboard) { + $this->showAd($action, array('id' => 'ad_leaderboard'), + $this->Leaderboard); + } + + if ($this->Rectangle) { + $this->showAd($action, array('id' => 'ad_rectangle'), + $this->Rectangle); + } + + return true; + } + + //WideSkyscraper ad + function onEndShowAside($action) + { + if (!$this->WideSkyscraper) { + return true; + } + + $this->showAd($action, array('id' => 'ad_wide-skyscraper'), + $this->WideSkyscraper); + + return true; + } + + //Output ad container + function showAd($action, $attr=array(), $value) + { + $classes = ($attr['class']) ? $attr['class'].' ' : ''; + + $action->elementStart('div', array('id' => $attr['id'], + 'class' => $classes.'ad')); + $action->raw($value); + $action->elementEnd('div'); + } + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'UAP', + 'version' => STATUSNET_VERSION, + 'author' => 'Sarven Capadisli', + 'homepage' => 'http://status.net/wiki/Plugin:UAP', + 'rawdescription' => + _m('Outputs ad placements based on Universal Ad Package')); + return true; + } +} diff --git a/plugins/UAP/uap.css b/plugins/UAP/uap.css new file mode 100644 index 000000000..90abe5180 --- /dev/null +++ b/plugins/UAP/uap.css @@ -0,0 +1,50 @@ +/** Universal Ad Package styles: + * Medium Rectangle 300x250 + * Rectangle 180x150 + * Leaderboard 728x90 + * Wide Skyscraper 160x600 + * + * @package StatusNet + * @author Sarven Capadisli + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + + +.ad { +border:1px solid #CCC; +float:left; +} + +#ad_medium-rectangle { +width:300px; +height:250px; + +margin-left:1.35%; +margin-bottom:18px; +} + +#ad_rectangle { +width:180px; +height:150px; + +float:right; +margin-right:18px; +} + +#ad_leaderboard { +width:728px; +height:90px; + +margin:29px 18px 0 0; +clear:both; +} + +#ad_wide-skyscraper { +width:160px; +height:600px; + +margin-top:18px; +margin-left:8.5%; +} -- cgit v1.2.3-54-g00ecf From 58fde0dcb5342cb3d3ff44c2e54a077eea0e063e Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 21 Jan 2010 18:28:02 +0100 Subject: Lowercased switch cases in UAP Plugin --- plugins/UAP/UAPPlugin.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/UAP/UAPPlugin.php b/plugins/UAP/UAPPlugin.php index 13c101723..f3b16548d 100644 --- a/plugins/UAP/UAPPlugin.php +++ b/plugins/UAP/UAPPlugin.php @@ -72,16 +72,16 @@ class UAPPlugin extends Plugin { foreach($this->uap as $key => $value) { switch(strtolower($key)) { - case 'MediumRectangle': default: + case 'mediumrectangle': default: $this->MediumRectangle = $value; break; - case 'Rectangle': + case 'rectangle': $this->Rectangle = $value; break; - case 'Leaderboard': + case 'leaderboard': $this->Leaderboard = $value; break; - case 'WideSkyscraper': + case 'wideskyscraper': $this->WideSkyscraper = $value; break; } -- cgit v1.2.3-54-g00ecf From 1758ed453bcf2abd11bc2fde8768632099fe0de3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 06:25:22 -0500 Subject: move UAP plugin to core --- lib/uapplugin.php | 176 ++++++++++++++++++++++++++++++++++++++++++++++ plugins/UAP/UAPPlugin.php | 176 ---------------------------------------------- plugins/UAP/uap.css | 50 ------------- theme/base/css/uap.css | 50 +++++++++++++ 4 files changed, 226 insertions(+), 226 deletions(-) create mode 100644 lib/uapplugin.php delete mode 100644 plugins/UAP/UAPPlugin.php delete mode 100644 plugins/UAP/uap.css create mode 100644 theme/base/css/uap.css diff --git a/lib/uapplugin.php b/lib/uapplugin.php new file mode 100644 index 000000000..f3b16548d --- /dev/null +++ b/lib/uapplugin.php @@ -0,0 +1,176 @@ +. + * + * @category Action + * @package StatusNet + * @author Sarven Capadisli + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Outputs the following ad types (based on UAP): + * Medium Rectangle 300x250 + * Rectangle 180x150 + * Leaderboard 728x90 + * Wide Skyscraper 160x600 + * + * Any number of ad types can be used. Enable all using example: + * addPlugin('UAP', array( + * 'MediumRectangle' => '', + * 'Rectangle' => '', + * 'Leaderboard' => '', + * 'WideSkyscraper' => '' + * ) + * ); + * + * @category Plugin + * @package StatusNet + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class UAPPlugin extends Plugin +{ + public $MediumRectangle = null; + public $Rectangle = null; + public $Leaderboard = null; + public $WideSkyscraper = null; + + function __construct($uap = array()) + { + $this->uap = $uap; + + parent::__construct(); + } + + function onInitializePlugin() + { + foreach($this->uap as $key => $value) { + switch(strtolower($key)) { + case 'mediumrectangle': default: + $this->MediumRectangle = $value; + break; + case 'rectangle': + $this->Rectangle = $value; + break; + case 'leaderboard': + $this->Leaderboard = $value; + break; + case 'wideskyscraper': + $this->WideSkyscraper = $value; + break; + } + } + } + + function onEndShowStatusNetStyles($action) + { + $action->cssLink(common_path('plugins/UAP/uap.css'), + null, 'screen, projection, tv'); + return true; + } + + //MediumRectangle ad + function onStartShowAside($action) + { + if (!$this->MediumRectangle) { + return true; + } + + $this->showAd($action, array('id' => 'ad_medium-rectangle'), + $this->MediumRectangle); + + return true; + } + +/* + //Rectangle ad + function onEndShowSiteNotice($action) + { + if (!$this->Rectangle) { + return true; + } + + $this->showAd($action, array('id' => 'ad_rectangle'), + $this->Rectangle); + + return true; + } +*/ + + //Leaderboard and Rectangle ad + function onStartShowHeader($action) + { + if ($this->Leaderboard) { + $this->showAd($action, array('id' => 'ad_leaderboard'), + $this->Leaderboard); + } + + if ($this->Rectangle) { + $this->showAd($action, array('id' => 'ad_rectangle'), + $this->Rectangle); + } + + return true; + } + + //WideSkyscraper ad + function onEndShowAside($action) + { + if (!$this->WideSkyscraper) { + return true; + } + + $this->showAd($action, array('id' => 'ad_wide-skyscraper'), + $this->WideSkyscraper); + + return true; + } + + //Output ad container + function showAd($action, $attr=array(), $value) + { + $classes = ($attr['class']) ? $attr['class'].' ' : ''; + + $action->elementStart('div', array('id' => $attr['id'], + 'class' => $classes.'ad')); + $action->raw($value); + $action->elementEnd('div'); + } + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'UAP', + 'version' => STATUSNET_VERSION, + 'author' => 'Sarven Capadisli', + 'homepage' => 'http://status.net/wiki/Plugin:UAP', + 'rawdescription' => + _m('Outputs ad placements based on Universal Ad Package')); + return true; + } +} diff --git a/plugins/UAP/UAPPlugin.php b/plugins/UAP/UAPPlugin.php deleted file mode 100644 index f3b16548d..000000000 --- a/plugins/UAP/UAPPlugin.php +++ /dev/null @@ -1,176 +0,0 @@ -. - * - * @category Action - * @package StatusNet - * @author Sarven Capadisli - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -/** - * Outputs the following ad types (based on UAP): - * Medium Rectangle 300x250 - * Rectangle 180x150 - * Leaderboard 728x90 - * Wide Skyscraper 160x600 - * - * Any number of ad types can be used. Enable all using example: - * addPlugin('UAP', array( - * 'MediumRectangle' => '', - * 'Rectangle' => '', - * 'Leaderboard' => '', - * 'WideSkyscraper' => '' - * ) - * ); - * - * @category Plugin - * @package StatusNet - * @author Sarven Capadisli - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -class UAPPlugin extends Plugin -{ - public $MediumRectangle = null; - public $Rectangle = null; - public $Leaderboard = null; - public $WideSkyscraper = null; - - function __construct($uap = array()) - { - $this->uap = $uap; - - parent::__construct(); - } - - function onInitializePlugin() - { - foreach($this->uap as $key => $value) { - switch(strtolower($key)) { - case 'mediumrectangle': default: - $this->MediumRectangle = $value; - break; - case 'rectangle': - $this->Rectangle = $value; - break; - case 'leaderboard': - $this->Leaderboard = $value; - break; - case 'wideskyscraper': - $this->WideSkyscraper = $value; - break; - } - } - } - - function onEndShowStatusNetStyles($action) - { - $action->cssLink(common_path('plugins/UAP/uap.css'), - null, 'screen, projection, tv'); - return true; - } - - //MediumRectangle ad - function onStartShowAside($action) - { - if (!$this->MediumRectangle) { - return true; - } - - $this->showAd($action, array('id' => 'ad_medium-rectangle'), - $this->MediumRectangle); - - return true; - } - -/* - //Rectangle ad - function onEndShowSiteNotice($action) - { - if (!$this->Rectangle) { - return true; - } - - $this->showAd($action, array('id' => 'ad_rectangle'), - $this->Rectangle); - - return true; - } -*/ - - //Leaderboard and Rectangle ad - function onStartShowHeader($action) - { - if ($this->Leaderboard) { - $this->showAd($action, array('id' => 'ad_leaderboard'), - $this->Leaderboard); - } - - if ($this->Rectangle) { - $this->showAd($action, array('id' => 'ad_rectangle'), - $this->Rectangle); - } - - return true; - } - - //WideSkyscraper ad - function onEndShowAside($action) - { - if (!$this->WideSkyscraper) { - return true; - } - - $this->showAd($action, array('id' => 'ad_wide-skyscraper'), - $this->WideSkyscraper); - - return true; - } - - //Output ad container - function showAd($action, $attr=array(), $value) - { - $classes = ($attr['class']) ? $attr['class'].' ' : ''; - - $action->elementStart('div', array('id' => $attr['id'], - 'class' => $classes.'ad')); - $action->raw($value); - $action->elementEnd('div'); - } - - function onPluginVersion(&$versions) - { - $versions[] = array('name' => 'UAP', - 'version' => STATUSNET_VERSION, - 'author' => 'Sarven Capadisli', - 'homepage' => 'http://status.net/wiki/Plugin:UAP', - 'rawdescription' => - _m('Outputs ad placements based on Universal Ad Package')); - return true; - } -} diff --git a/plugins/UAP/uap.css b/plugins/UAP/uap.css deleted file mode 100644 index 90abe5180..000000000 --- a/plugins/UAP/uap.css +++ /dev/null @@ -1,50 +0,0 @@ -/** Universal Ad Package styles: - * Medium Rectangle 300x250 - * Rectangle 180x150 - * Leaderboard 728x90 - * Wide Skyscraper 160x600 - * - * @package StatusNet - * @author Sarven Capadisli - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - - -.ad { -border:1px solid #CCC; -float:left; -} - -#ad_medium-rectangle { -width:300px; -height:250px; - -margin-left:1.35%; -margin-bottom:18px; -} - -#ad_rectangle { -width:180px; -height:150px; - -float:right; -margin-right:18px; -} - -#ad_leaderboard { -width:728px; -height:90px; - -margin:29px 18px 0 0; -clear:both; -} - -#ad_wide-skyscraper { -width:160px; -height:600px; - -margin-top:18px; -margin-left:8.5%; -} diff --git a/theme/base/css/uap.css b/theme/base/css/uap.css new file mode 100644 index 000000000..90abe5180 --- /dev/null +++ b/theme/base/css/uap.css @@ -0,0 +1,50 @@ +/** Universal Ad Package styles: + * Medium Rectangle 300x250 + * Rectangle 180x150 + * Leaderboard 728x90 + * Wide Skyscraper 160x600 + * + * @package StatusNet + * @author Sarven Capadisli + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + + +.ad { +border:1px solid #CCC; +float:left; +} + +#ad_medium-rectangle { +width:300px; +height:250px; + +margin-left:1.35%; +margin-bottom:18px; +} + +#ad_rectangle { +width:180px; +height:150px; + +float:right; +margin-right:18px; +} + +#ad_leaderboard { +width:728px; +height:90px; + +margin:29px 18px 0 0; +clear:both; +} + +#ad_wide-skyscraper { +width:160px; +height:600px; + +margin-top:18px; +margin-left:8.5%; +} -- cgit v1.2.3-54-g00ecf From 7c54591472b4ff997ea099792c95d40b98798381 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 07:19:36 -0500 Subject: make uapplugin an abstract class --- lib/uapplugin.php | 199 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 105 insertions(+), 94 deletions(-) diff --git a/lib/uapplugin.php b/lib/uapplugin.php index f3b16548d..5160d9662 100644 --- a/lib/uapplugin.php +++ b/lib/uapplugin.php @@ -22,6 +22,7 @@ * @category Action * @package StatusNet * @author Sarven Capadisli + * @author Evan Prodromou * @copyright 2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ @@ -32,145 +33,155 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { } /** + * Abstract superclass for advertising plugins + * + * Plugins for showing ads should derive from this plugin. + * * Outputs the following ad types (based on UAP): + * * Medium Rectangle 300x250 * Rectangle 180x150 * Leaderboard 728x90 * Wide Skyscraper 160x600 * - * Any number of ad types can be used. Enable all using example: - * addPlugin('UAP', array( - * 'MediumRectangle' => '', - * 'Rectangle' => '', - * 'Leaderboard' => '', - * 'WideSkyscraper' => '' - * ) - * ); - * * @category Plugin * @package StatusNet * @author Sarven Capadisli + * @author Evan Prodromou * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ -class UAPPlugin extends Plugin +abstract class UAPPlugin extends Plugin { public $MediumRectangle = null; - public $Rectangle = null; - public $Leaderboard = null; - public $WideSkyscraper = null; - - function __construct($uap = array()) - { - $this->uap = $uap; - - parent::__construct(); - } - - function onInitializePlugin() - { - foreach($this->uap as $key => $value) { - switch(strtolower($key)) { - case 'mediumrectangle': default: - $this->MediumRectangle = $value; - break; - case 'rectangle': - $this->Rectangle = $value; - break; - case 'leaderboard': - $this->Leaderboard = $value; - break; - case 'wideskyscraper': - $this->WideSkyscraper = $value; - break; - } - } - } + public $Rectangle = null; + public $Leaderboard = null; + public $WideSkyscraper = null; + + /** + * Output our dedicated stylesheet + * + * @param Action $action Action being shown + * + * @return boolean hook flag + */ function onEndShowStatusNetStyles($action) { - $action->cssLink(common_path('plugins/UAP/uap.css'), - null, 'screen, projection, tv'); + // XXX: allow override by theme + $action->cssLink('css/uap.css', 'base', 'screen, projection, tv'); return true; } - //MediumRectangle ad + /** + * Add a medium rectangle ad at the beginning of sidebar + * + * @param Action $action Action being shown + * + * @return boolean hook flag + */ + function onStartShowAside($action) { - if (!$this->MediumRectangle) { - return true; - } + if (!is_null($this->mediumRectangle)) { - $this->showAd($action, array('id' => 'ad_medium-rectangle'), - $this->MediumRectangle); + $action->elementStart('div', + array('class' => 'ad_medium-rectangle ad')); - return true; - } + $this->showMediumRectangle($action); -/* - //Rectangle ad - function onEndShowSiteNotice($action) - { - if (!$this->Rectangle) { - return true; + $action->elementEnd('div'); } - $this->showAd($action, array('id' => 'ad_rectangle'), - $this->Rectangle); - return true; } -*/ - //Leaderboard and Rectangle ad + /** + * Add a leaderboard and/or rectangle in the header + * + * @param Action $action Action being shown + * + * @return boolean hook flag + */ + function onStartShowHeader($action) { - if ($this->Leaderboard) { - $this->showAd($action, array('id' => 'ad_leaderboard'), - $this->Leaderboard); + if (!is_null($this->leaderboard)) { + $action->elementStart('div', + array('class' => 'ad_leaderboard ad')); + $this->showLeaderboard($action); + $action->elementEnd('div'); } - if ($this->Rectangle) { - $this->showAd($action, array('id' => 'ad_rectangle'), - $this->Rectangle); + if (!is_null($this->rectangle)) { + $action->elementStart('div', + array('class' => 'ad_rectangle ad')); + $this->showRectangle($action); + $action->elementEnd('div'); } return true; } - //WideSkyscraper ad + /** + * Add a wide skyscraper after the aside + * + * @param Action $action Action being shown + * + * @return boolean hook flag + */ + function onEndShowAside($action) { - if (!$this->WideSkyscraper) { - return true; - } + if (!is_null($this->wideSkyscraper)) { + $action->elementStart('div', + array('class' => 'ad_wide-skyscraper ad')); - $this->showAd($action, array('id' => 'ad_wide-skyscraper'), - $this->WideSkyscraper); + $this->showWideSkyscraper($action); + $action->elementEnd('div'); + } return true; } - //Output ad container - function showAd($action, $attr=array(), $value) - { - $classes = ($attr['class']) ? $attr['class'].' ' : ''; - - $action->elementStart('div', array('id' => $attr['id'], - 'class' => $classes.'ad')); - $action->raw($value); - $action->elementEnd('div'); - } - - function onPluginVersion(&$versions) - { - $versions[] = array('name' => 'UAP', - 'version' => STATUSNET_VERSION, - 'author' => 'Sarven Capadisli', - 'homepage' => 'http://status.net/wiki/Plugin:UAP', - 'rawdescription' => - _m('Outputs ad placements based on Universal Ad Package')); - return true; - } + /** + * Show a medium rectangle ad + * + * @param Action $action Action being shown + * + * @return void + */ + + abstract protected function showMediumRectangle($action); + + /** + * Show a rectangle ad + * + * @param Action $action Action being shown + * + * @return void + */ + + abstract protected function showRectangle($action); + + /** + * Show a wide skyscraper ad + * + * @param Action $action Action being shown + * + * @return void + */ + + abstract protected function showWideSkyscraper($action); + + /** + * Show a leaderboard ad + * + * @param Action $action Action being shown + * + * @return void + */ + + abstract protected function showLeaderboard($action); } -- cgit v1.2.3-54-g00ecf From 9decd9806c675ce820a1443507846ba9c57ec72b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 07:26:32 -0500 Subject: Add BlankAdPlugin to test ad layout in different themes --- plugins/BlankAdPlugin.php | 121 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 plugins/BlankAdPlugin.php diff --git a/plugins/BlankAdPlugin.php b/plugins/BlankAdPlugin.php new file mode 100644 index 000000000..bf3a225c0 --- /dev/null +++ b/plugins/BlankAdPlugin.php @@ -0,0 +1,121 @@ +. + * + * @category Ads + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugin for testing ad layout + * + * This plugin uses the UAPPlugin framework to output ad content. However, + * its ad content is just paragraphs with defined background colors. It's + * mostly useful for debugging theme layout. + * + * To use this plugin, set the parameter for the ad size you want to use + * to the background you want to use. For example, to make a leaderboard + * that's red: + * + * addPlugin('BlankAd', array('leaderboard' => 'red')); + * + * @category Plugin + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @seeAlso Location + */ + +class BlankAdPlugin extends UAPPlugin +{ + /** + * Show a medium rectangle 'ad' + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showMediumRectangle($action) + { + $style = 'width: 300px; height: 250px; background-color: ' . + $this->mediumRectangle; + + $action->element('p', array('style' => $style), ''); + } + + /** + * Show a rectangle 'ad' + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showRectangle($action) + { + $style = 'width: 180px; height: 150px; background-color: ' . + $this->rectangle; + + $action->element('p', array('style' => $style), ''); + } + + /** + * Show a wide skyscraper ad + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showWideSkyscraper($action) + { + $style = 'width: 160px; height: 600px; background-color: ' . + $this->wideSkyscraper; + + $action->element('p', array('style' => $style), ''); + } + + /** + * Show a leaderboard ad + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showLeaderboard($action) + { + $style = 'width: 728px; height: 90px; background-color: ' . + $this->leaderboard; + + $action->element('p', array('style' => $style), ''); + } +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf From b412ebab11e4ef55bc06b6ea5dfe11fd66614f50 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 07:34:31 -0500 Subject: move BlankAdPlugin to its own dir --- plugins/BlankAd/BlankAdPlugin.php | 121 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 plugins/BlankAd/BlankAdPlugin.php diff --git a/plugins/BlankAd/BlankAdPlugin.php b/plugins/BlankAd/BlankAdPlugin.php new file mode 100644 index 000000000..bf3a225c0 --- /dev/null +++ b/plugins/BlankAd/BlankAdPlugin.php @@ -0,0 +1,121 @@ +. + * + * @category Ads + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugin for testing ad layout + * + * This plugin uses the UAPPlugin framework to output ad content. However, + * its ad content is just paragraphs with defined background colors. It's + * mostly useful for debugging theme layout. + * + * To use this plugin, set the parameter for the ad size you want to use + * to the background you want to use. For example, to make a leaderboard + * that's red: + * + * addPlugin('BlankAd', array('leaderboard' => 'red')); + * + * @category Plugin + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @seeAlso Location + */ + +class BlankAdPlugin extends UAPPlugin +{ + /** + * Show a medium rectangle 'ad' + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showMediumRectangle($action) + { + $style = 'width: 300px; height: 250px; background-color: ' . + $this->mediumRectangle; + + $action->element('p', array('style' => $style), ''); + } + + /** + * Show a rectangle 'ad' + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showRectangle($action) + { + $style = 'width: 180px; height: 150px; background-color: ' . + $this->rectangle; + + $action->element('p', array('style' => $style), ''); + } + + /** + * Show a wide skyscraper ad + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showWideSkyscraper($action) + { + $style = 'width: 160px; height: 600px; background-color: ' . + $this->wideSkyscraper; + + $action->element('p', array('style' => $style), ''); + } + + /** + * Show a leaderboard ad + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showLeaderboard($action) + { + $style = 'width: 728px; height: 90px; background-color: ' . + $this->leaderboard; + + $action->element('p', array('style' => $style), ''); + } +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf From b2b95bd21fa3995b77af5096da80d52bf6938c3b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 07:41:12 -0500 Subject: make BlankAd dir and change to use a 1x1 image --- plugins/BlankAd/redpixel.png | Bin 0 -> 159 bytes plugins/BlankAdPlugin.php | 121 ------------------------------------------- 2 files changed, 121 deletions(-) create mode 100644 plugins/BlankAd/redpixel.png delete mode 100644 plugins/BlankAdPlugin.php diff --git a/plugins/BlankAd/redpixel.png b/plugins/BlankAd/redpixel.png new file mode 100644 index 000000000..26299a552 Binary files /dev/null and b/plugins/BlankAd/redpixel.png differ diff --git a/plugins/BlankAdPlugin.php b/plugins/BlankAdPlugin.php deleted file mode 100644 index bf3a225c0..000000000 --- a/plugins/BlankAdPlugin.php +++ /dev/null @@ -1,121 +0,0 @@ -. - * - * @category Ads - * @package StatusNet - * @author Evan Prodromou - * @copyright 2010 StatusNet Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - exit(1); -} - -/** - * Plugin for testing ad layout - * - * This plugin uses the UAPPlugin framework to output ad content. However, - * its ad content is just paragraphs with defined background colors. It's - * mostly useful for debugging theme layout. - * - * To use this plugin, set the parameter for the ad size you want to use - * to the background you want to use. For example, to make a leaderboard - * that's red: - * - * addPlugin('BlankAd', array('leaderboard' => 'red')); - * - * @category Plugin - * @package StatusNet - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * - * @seeAlso Location - */ - -class BlankAdPlugin extends UAPPlugin -{ - /** - * Show a medium rectangle 'ad' - * - * @param Action $action Action being shown - * - * @return void - */ - - protected function showMediumRectangle($action) - { - $style = 'width: 300px; height: 250px; background-color: ' . - $this->mediumRectangle; - - $action->element('p', array('style' => $style), ''); - } - - /** - * Show a rectangle 'ad' - * - * @param Action $action Action being shown - * - * @return void - */ - - protected function showRectangle($action) - { - $style = 'width: 180px; height: 150px; background-color: ' . - $this->rectangle; - - $action->element('p', array('style' => $style), ''); - } - - /** - * Show a wide skyscraper ad - * - * @param Action $action Action being shown - * - * @return void - */ - - protected function showWideSkyscraper($action) - { - $style = 'width: 160px; height: 600px; background-color: ' . - $this->wideSkyscraper; - - $action->element('p', array('style' => $style), ''); - } - - /** - * Show a leaderboard ad - * - * @param Action $action Action being shown - * - * @return void - */ - - protected function showLeaderboard($action) - { - $style = 'width: 728px; height: 90px; background-color: ' . - $this->leaderboard; - - $action->element('p', array('style' => $style), ''); - } -} \ No newline at end of file -- cgit v1.2.3-54-g00ecf From e4393ee6dbcfbcf660d898dd1eb5fe91c4e3b80d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 07:51:25 -0500 Subject: Add the moved BlankAdPlugin --- plugins/BlankAd/BlankAdPlugin.php | 45 +++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/plugins/BlankAd/BlankAdPlugin.php b/plugins/BlankAd/BlankAdPlugin.php index bf3a225c0..5f46ddd48 100644 --- a/plugins/BlankAd/BlankAdPlugin.php +++ b/plugins/BlankAd/BlankAdPlugin.php @@ -35,14 +35,13 @@ if (!defined('STATUSNET')) { * Plugin for testing ad layout * * This plugin uses the UAPPlugin framework to output ad content. However, - * its ad content is just paragraphs with defined background colors. It's - * mostly useful for debugging theme layout. + * its ad content is just images with one red pixel stretched to the + * right size. It's mostly useful for debugging theme layout. * * To use this plugin, set the parameter for the ad size you want to use - * to the background you want to use. For example, to make a leaderboard - * that's red: + * to true (or anything non-null). For example, to make a leaderboard: * - * addPlugin('BlankAd', array('leaderboard' => 'red')); + * addPlugin('BlankAd', array('leaderboard' => true)); * * @category Plugin * @package StatusNet @@ -65,10 +64,11 @@ class BlankAdPlugin extends UAPPlugin protected function showMediumRectangle($action) { - $style = 'width: 300px; height: 250px; background-color: ' . - $this->mediumRectangle; - - $action->element('p', array('style' => $style), ''); + $action->element('img', + array('width' => 300, + 'height' => 250, + 'src' => common_path('plugins/BlankAd/redpixel.png')), + ''); } /** @@ -81,10 +81,11 @@ class BlankAdPlugin extends UAPPlugin protected function showRectangle($action) { - $style = 'width: 180px; height: 150px; background-color: ' . - $this->rectangle; - - $action->element('p', array('style' => $style), ''); + $action->element('img', + array('width' => 180, + 'height' => 50, + 'src' => common_path('plugins/BlankAd/redpixel.png')), + ''); } /** @@ -97,10 +98,11 @@ class BlankAdPlugin extends UAPPlugin protected function showWideSkyscraper($action) { - $style = 'width: 160px; height: 600px; background-color: ' . - $this->wideSkyscraper; - - $action->element('p', array('style' => $style), ''); + $action->element('img', + array('width' => 160, + 'height' => 600, + 'src' => common_path('plugins/BlankAd/redpixel.png')), + ''); } /** @@ -113,9 +115,10 @@ class BlankAdPlugin extends UAPPlugin protected function showLeaderboard($action) { - $style = 'width: 728px; height: 90px; background-color: ' . - $this->leaderboard; - - $action->element('p', array('style' => $style), ''); + $action->element('img', + array('width' => 728, + 'height' => 90, + 'src' => common_path('plugins/BlankAd/redpixel.png')), + ''); } } \ No newline at end of file -- cgit v1.2.3-54-g00ecf From 4ad931ad38f1eba1f5b00a923854544dcb4aa0d6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 08:13:05 -0500 Subject: wrong height for rectangle in BlankAd --- plugins/BlankAd/BlankAdPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/BlankAd/BlankAdPlugin.php b/plugins/BlankAd/BlankAdPlugin.php index 5f46ddd48..0e2719aed 100644 --- a/plugins/BlankAd/BlankAdPlugin.php +++ b/plugins/BlankAd/BlankAdPlugin.php @@ -83,7 +83,7 @@ class BlankAdPlugin extends UAPPlugin { $action->element('img', array('width' => 180, - 'height' => 50, + 'height' => 150, 'src' => common_path('plugins/BlankAd/redpixel.png')), ''); } -- cgit v1.2.3-54-g00ecf From e9feafc3ca4641bf5c51bbced3454962ed89b8ef Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 08:13:24 -0500 Subject: CSS ids and classes fixed in UAPPlugin --- lib/uapplugin.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/uapplugin.php b/lib/uapplugin.php index 5160d9662..4364b1e19 100644 --- a/lib/uapplugin.php +++ b/lib/uapplugin.php @@ -87,7 +87,8 @@ abstract class UAPPlugin extends Plugin if (!is_null($this->mediumRectangle)) { $action->elementStart('div', - array('class' => 'ad_medium-rectangle ad')); + array('id' => 'ad_medium-rectangle', + 'class' => 'ad')); $this->showMediumRectangle($action); @@ -109,14 +110,16 @@ abstract class UAPPlugin extends Plugin { if (!is_null($this->leaderboard)) { $action->elementStart('div', - array('class' => 'ad_leaderboard ad')); + array('id' => 'ad_leaderboard', + 'class' => 'ad')); $this->showLeaderboard($action); $action->elementEnd('div'); } if (!is_null($this->rectangle)) { $action->elementStart('div', - array('class' => 'ad_rectangle ad')); + array('id' => 'ad_rectangle', + 'class' => 'ad')); $this->showRectangle($action); $action->elementEnd('div'); } @@ -136,7 +139,8 @@ abstract class UAPPlugin extends Plugin { if (!is_null($this->wideSkyscraper)) { $action->elementStart('div', - array('class' => 'ad_wide-skyscraper ad')); + array('id' => 'ad_wide-skyscraper', + 'class' => 'ad')); $this->showWideSkyscraper($action); -- cgit v1.2.3-54-g00ecf From e3bd97bfe46ee7d063a45eec282b28faee1ca83f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 27 Jan 2010 15:14:00 +0100 Subject: Aligning wide skyscraper to the right instead of left --- theme/base/css/uap.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/theme/base/css/uap.css b/theme/base/css/uap.css index 90abe5180..b95e9b2ce 100644 --- a/theme/base/css/uap.css +++ b/theme/base/css/uap.css @@ -45,6 +45,7 @@ clear:both; width:160px; height:600px; +float:right; margin-top:18px; -margin-left:8.5%; +margin-right:8.25%; } -- cgit v1.2.3-54-g00ecf From 1c875a53952506ca95716992511421d110a37d4d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 27 Jan 2010 15:32:59 +0100 Subject: Moved rectangle ad into aside and leaderboard to the right in header. Intention for this layout was to reduce whitespace in header area --- lib/uapplugin.php | 15 ++++++++++++++- theme/base/css/uap.css | 9 ++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/uapplugin.php b/lib/uapplugin.php index 4364b1e19..69b68f936 100644 --- a/lib/uapplugin.php +++ b/lib/uapplugin.php @@ -99,7 +99,7 @@ abstract class UAPPlugin extends Plugin } /** - * Add a leaderboard and/or rectangle in the header + * Add a leaderboard in the header * * @param Action $action Action being shown * @@ -116,6 +116,19 @@ abstract class UAPPlugin extends Plugin $action->elementEnd('div'); } + return true; + } + + /** + * Add a rectangle before aside sections + * + * @param Action $action Action being shown + * + * @return boolean hook flag + */ + + function onStartShowSections($action) + { if (!is_null($this->rectangle)) { $action->elementStart('div', array('id' => 'ad_rectangle', diff --git a/theme/base/css/uap.css b/theme/base/css/uap.css index b95e9b2ce..d7fa02c7f 100644 --- a/theme/base/css/uap.css +++ b/theme/base/css/uap.css @@ -29,15 +29,18 @@ margin-bottom:18px; width:180px; height:150px; -float:right; -margin-right:18px; +float:none; +clear:both; +margin:0 auto; +margin-bottom:29px; } #ad_leaderboard { width:728px; height:90px; -margin:29px 18px 0 0; +margin:11px 18px 0 0; +float:right; clear:both; } -- cgit v1.2.3-54-g00ecf From ea123800e991352d862c05a2f81e82ef53e72eeb Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 10:44:49 -0500 Subject: move leaderboard to after the header --- lib/uapplugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/uapplugin.php b/lib/uapplugin.php index 69b68f936..e3801f08a 100644 --- a/lib/uapplugin.php +++ b/lib/uapplugin.php @@ -106,7 +106,7 @@ abstract class UAPPlugin extends Plugin * @return boolean hook flag */ - function onStartShowHeader($action) + function onEndShowHeader($action) { if (!is_null($this->leaderboard)) { $action->elementStart('div', -- cgit v1.2.3-54-g00ecf From 760be76fc000c546ac38e96fa1cc5f822b7e6993 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 10:45:44 -0500 Subject: camelcase the uap param names --- lib/uapplugin.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/uapplugin.php b/lib/uapplugin.php index e3801f08a..ef35bafbf 100644 --- a/lib/uapplugin.php +++ b/lib/uapplugin.php @@ -54,10 +54,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { abstract class UAPPlugin extends Plugin { - public $MediumRectangle = null; - public $Rectangle = null; - public $Leaderboard = null; - public $WideSkyscraper = null; + public $mediumRectangle = null; + public $rectangle = null; + public $leaderboard = null; + public $wideSkyscraper = null; /** * Output our dedicated stylesheet -- cgit v1.2.3-54-g00ecf From 5e31ecd82aaf102382380b1352503303893cf9ba Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 27 Jan 2010 16:52:18 +0100 Subject: Centred leaderboard ad --- theme/base/css/uap.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/theme/base/css/uap.css b/theme/base/css/uap.css index d7fa02c7f..73be5f0c1 100644 --- a/theme/base/css/uap.css +++ b/theme/base/css/uap.css @@ -39,8 +39,8 @@ margin-bottom:29px; width:728px; height:90px; -margin:11px 18px 0 0; -float:right; +margin:0 auto 18px; +float:none; clear:both; } -- cgit v1.2.3-54-g00ecf From 47645228da4a9af8ffb0dc1f11c1da995f4791cb Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 13:58:55 -0800 Subject: Add new oauth tables and modifications to 'consumer' table for rc4 --- db/rc3to09.sql | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/db/rc3to09.sql b/db/rc3to09.sql index 02dc7a6e2..8342c4bc6 100644 --- a/db/rc3to09.sql +++ b/db/rc3to09.sql @@ -14,3 +14,35 @@ insert into queue_item_new (frame,transport,created,claimed) alter table queue_item rename to queue_item_old; alter table queue_item_new rename to queue_item; +alter table consumer + add consumer_secret varchar(255) not null comment 'secret value', + add verifier varchar(255) comment 'verifier string for OAuth 1.0a', + add verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a'; + +create table oauth_application ( + id integer auto_increment primary key comment 'unique identifier', + owner integer not null comment 'owner of the application' references profile (id), + consumer_key varchar(255) not null comment 'application consumer key' references consumer (consumer_key), + name varchar(255) not null comment 'name of the application', + description varchar(255) comment 'description of the application', + icon varchar(255) not null comment 'application icon', + source_url varchar(255) comment 'application homepage - used for source link', + organization varchar(255) comment 'name of the organization running the application', + homepage varchar(255) comment 'homepage for the organization', + callback_url varchar(255) comment 'url to redirect to after authentication', + type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop', + access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write', + created datetime not null comment 'date this record was created', + modified timestamp comment 'date this record was modified' +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +create table oauth_application_user ( + profile_id integer not null comment 'user of the application' references profile (id), + application_id integer not null comment 'id of the application' references oauth_application (id), + access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked', + token varchar(255) comment 'request or access token', + created datetime not null comment 'date this record was created', + modified timestamp comment 'date this record was modified', + constraint primary key (profile_id, application_id) +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + -- cgit v1.2.3-54-g00ecf From 9a54745fcd252e27cc664da7cee67b48d0f4f501 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 13:59:58 -0800 Subject: Rename rc3to09.sql to rc3torc4.sql to avoid confusion if we add a last-minute change after this! --- db/rc3to09.sql | 48 ------------------------------------------------ db/rc3torc4.sql | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 48 deletions(-) delete mode 100644 db/rc3to09.sql create mode 100644 db/rc3torc4.sql diff --git a/db/rc3to09.sql b/db/rc3to09.sql deleted file mode 100644 index 8342c4bc6..000000000 --- a/db/rc3to09.sql +++ /dev/null @@ -1,48 +0,0 @@ -create table queue_item_new ( - id integer auto_increment primary key comment 'unique identifier', - frame blob not null comment 'data: object reference or opaque string', - transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...', - created datetime not null comment 'date this record was created', - claimed datetime comment 'date this item was claimed', - - index queue_item_created_idx (created) - -) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; - -insert into queue_item_new (frame,transport,created,claimed) - select notice_id,transport,created,claimed from queue_item; -alter table queue_item rename to queue_item_old; -alter table queue_item_new rename to queue_item; - -alter table consumer - add consumer_secret varchar(255) not null comment 'secret value', - add verifier varchar(255) comment 'verifier string for OAuth 1.0a', - add verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a'; - -create table oauth_application ( - id integer auto_increment primary key comment 'unique identifier', - owner integer not null comment 'owner of the application' references profile (id), - consumer_key varchar(255) not null comment 'application consumer key' references consumer (consumer_key), - name varchar(255) not null comment 'name of the application', - description varchar(255) comment 'description of the application', - icon varchar(255) not null comment 'application icon', - source_url varchar(255) comment 'application homepage - used for source link', - organization varchar(255) comment 'name of the organization running the application', - homepage varchar(255) comment 'homepage for the organization', - callback_url varchar(255) comment 'url to redirect to after authentication', - type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop', - access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write', - created datetime not null comment 'date this record was created', - modified timestamp comment 'date this record was modified' -) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; - -create table oauth_application_user ( - profile_id integer not null comment 'user of the application' references profile (id), - application_id integer not null comment 'id of the application' references oauth_application (id), - access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked', - token varchar(255) comment 'request or access token', - created datetime not null comment 'date this record was created', - modified timestamp comment 'date this record was modified', - constraint primary key (profile_id, application_id) -) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; - diff --git a/db/rc3torc4.sql b/db/rc3torc4.sql new file mode 100644 index 000000000..8342c4bc6 --- /dev/null +++ b/db/rc3torc4.sql @@ -0,0 +1,48 @@ +create table queue_item_new ( + id integer auto_increment primary key comment 'unique identifier', + frame blob not null comment 'data: object reference or opaque string', + transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...', + created datetime not null comment 'date this record was created', + claimed datetime comment 'date this item was claimed', + + index queue_item_created_idx (created) + +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +insert into queue_item_new (frame,transport,created,claimed) + select notice_id,transport,created,claimed from queue_item; +alter table queue_item rename to queue_item_old; +alter table queue_item_new rename to queue_item; + +alter table consumer + add consumer_secret varchar(255) not null comment 'secret value', + add verifier varchar(255) comment 'verifier string for OAuth 1.0a', + add verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a'; + +create table oauth_application ( + id integer auto_increment primary key comment 'unique identifier', + owner integer not null comment 'owner of the application' references profile (id), + consumer_key varchar(255) not null comment 'application consumer key' references consumer (consumer_key), + name varchar(255) not null comment 'name of the application', + description varchar(255) comment 'description of the application', + icon varchar(255) not null comment 'application icon', + source_url varchar(255) comment 'application homepage - used for source link', + organization varchar(255) comment 'name of the organization running the application', + homepage varchar(255) comment 'homepage for the organization', + callback_url varchar(255) comment 'url to redirect to after authentication', + type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop', + access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write', + created datetime not null comment 'date this record was created', + modified timestamp comment 'date this record was modified' +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +create table oauth_application_user ( + profile_id integer not null comment 'user of the application' references profile (id), + application_id integer not null comment 'id of the application' references oauth_application (id), + access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked', + token varchar(255) comment 'request or access token', + created datetime not null comment 'date this record was created', + modified timestamp comment 'date this record was modified', + constraint primary key (profile_id, application_id) +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + -- cgit v1.2.3-54-g00ecf From b0a325f7d0418575cdb46b7074c4cd2317f04980 Mon Sep 17 00:00:00 2001 From: Michele Date: Sun, 17 Jan 2010 11:21:07 +0100 Subject: HTTP auth provided is evaluated even if it's not required --- lib/apiauth.php | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/apiauth.php b/lib/apiauth.php index ad9651ff2..ac5e997c7 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -84,16 +84,22 @@ class ApiAuthAction extends ApiAction } else { $this->checkBasicAuthUser(); } + } else { - // Reject API calls with the wrong access level + // Check to see if a basic auth user is there even + // if one's not required - if ($this->isReadOnly($args) == false) { - if ($this->access != self::READ_WRITE) { - $msg = 'API resource requires read-write access, ' . - 'but you only have read access.'; - $this->clientError($msg, 401, $this->format); - exit(); - } + $this->checkBasicAuthUser(false); + } + + // Reject API calls with the wrong access level + + if ($this->isReadOnly($args) == false) { + if ($this->access != self::READ_WRITE) { + $msg = 'API resource requires read-write access, ' . + 'but you only have read access.'; + $this->clientError($msg, 401, $this->format); + exit; } } @@ -206,13 +212,13 @@ class ApiAuthAction extends ApiAction * @return boolean true or false */ - function checkBasicAuthUser() + function checkBasicAuthUser($required = true) { $this->basicAuthProcessHeader(); $realm = common_config('site', 'name') . ' API'; - if (!isset($this->auth_user_nickname)) { + if (!isset($this->auth_user_nickname) && $required) { header('WWW-Authenticate: Basic realm="' . $realm . '"'); // show error if the user clicks 'cancel' @@ -222,11 +228,10 @@ class ApiAuthAction extends ApiAction } else { - $user = common_check_user($this->auth_user_nickname, - $this->auth_user_password); - if (Event::handle('StartSetApiUser', array(&$user))) { - $this->auth_user = $user; + $this->auth_user = common_check_user($this->auth_user_nickname, + $this->auth_user_password); + Event::handle('EndSetApiUser', array($user)); } -- cgit v1.2.3-54-g00ecf From 00d9b215f4de97f94035880041179879fcf0d201 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 17:29:36 -0500 Subject: Plugin to support Google Adsense A plugin to easily add Google Adsense blocks to a StatusNet site. --- plugins/Adsense/AdsensePlugin.php | 160 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 plugins/Adsense/AdsensePlugin.php diff --git a/plugins/Adsense/AdsensePlugin.php b/plugins/Adsense/AdsensePlugin.php new file mode 100644 index 000000000..dc2b32bc8 --- /dev/null +++ b/plugins/Adsense/AdsensePlugin.php @@ -0,0 +1,160 @@ +. + * + * @category Ads + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugin to add Google Adsense to StatusNet sites + * + * This plugin lets you add Adsense ad units to your StatusNet site. + * + * We support the 4 ad sizes for the Universal Ad Platform (UAP): + * + * Medium Rectangle + * (Small) Rectangle + * Leaderboard + * Wide Skyscraper + * + * They fit in different places on the default theme. Some themes + * might interact quite poorly with this plugin. + * + * To enable advertising, you must sign up with Google Adsense and + * get a client ID. + * + * https://www.google.com/adsense/ + * + * You'll also need to create an Adsense for Content unit in one + * of the four sizes described above. At the end of the process, + * note the "google_ad_client" and "google_ad_slot" values in the + * resultant Javascript. + * + * Add the plugin to config.php like so: + * + * addPlugin('Adsense', array('client' => 'Your client ID', + * 'rectangle' => 'slot')); + * + * Here, your client ID is the value of google_ad_client and the + * slot is the value of google_ad_slot. Note that if you create + * a different size, you'll need to provide different arguments: + * 'mediumRectangle', 'leaderboard', or 'wideSkyscraper'. + * + * If for some reason your ad server is different from the default, + * use the 'adScript' parameter to set the full path to the ad script. + * + * @category Plugin + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @seeAlso UAPPlugin + */ + +class AdsensePlugin extends UAPPlugin +{ + public $adScript = 'http://pagead2.googlesyndication.com/pagead/show_ads.js'; + public $client = null; + + /** + * Show a medium rectangle 'ad' + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showMediumRectangle($action) + { + $this->showAdsenseCode($action, 300, 250, $this->mediumRectangle); + } + + /** + * Show a rectangle 'ad' + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showRectangle($action) + { + $this->showAdsenseCode($action, 180, 150, $this->rectangle); + } + + /** + * Show a wide skyscraper ad + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showWideSkyscraper($action) + { + $this->showAdsenseCode($action, 160, 600, $this->wideSkyscraper); + } + + /** + * Show a leaderboard ad + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showLeaderboard($action) + { + $this->showAdsenseCode($action, 728, 90, $this->leaderboard); + } + + /** + * Output the bits of JavaScript code to show Adsense + * + * @param Action $action Action being shown + * @param integer $width Width of the block + * @param integer $height Height of the block + * @param string $slot Slot identifier + * + * @return void + */ + + protected function showAdsenseCode($action, $width, $height, $slot) + { + $code = 'google_ad_client = "'.$this->client.'"; '; + $code .= 'google_ad_slot = "'.$slot.'"; '; + $code .= 'google_ad_width = "'.$width.'"; '; + $code .= 'google_ad_height = "'.$height.'"; '; + + $action->inlineScript($code); + + $action->script($this->adScript); + } +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf From 2aba2eeeaf78f4e407abe23643ff6027b0ea53dd Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 17:55:33 -0500 Subject: width and height should be integers in AdsensePlugin --- plugins/Adsense/AdsensePlugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Adsense/AdsensePlugin.php b/plugins/Adsense/AdsensePlugin.php index dc2b32bc8..ab2b9a6fb 100644 --- a/plugins/Adsense/AdsensePlugin.php +++ b/plugins/Adsense/AdsensePlugin.php @@ -150,8 +150,8 @@ class AdsensePlugin extends UAPPlugin { $code = 'google_ad_client = "'.$this->client.'"; '; $code .= 'google_ad_slot = "'.$slot.'"; '; - $code .= 'google_ad_width = "'.$width.'"; '; - $code .= 'google_ad_height = "'.$height.'"; '; + $code .= 'google_ad_width = '.$width.'; '; + $code .= 'google_ad_height = '.$height.'; '; $action->inlineScript($code); -- cgit v1.2.3-54-g00ecf From 06cd3358970bfc27ff027cd1d8dc6974950c3793 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 10:08:24 -0800 Subject: Add scripts/sendemail.php to send email to a user's address. Updated setup_status_network.sh to create a user with the site's nick, accept site tags, and send a mail to the user (if a template is set) Email and tag params added to the end: setup_status_net.sh mysite 'My Site' 'owner@example.com' '1user' (If multiple tags are needed, separate them with a pipe "|". Be sure to quote properly!) New parameters for setup.cfg need to be set: export PHPBASE=/var/www/statusnet export WILDCARD=example.net export MAILTEMPLATE=/etc/statusnet/newsite-mail.txt export MAILSUBJECT="Your new StatusNet site" $PHPBASE is the base dir for a callable StatusNet install, used to run command-line scripts for user setup. $WILDCARD is the wildcard domain, needed to build a full server name to pass into command-line scripts. $MAILTEMPLATE points to a file containing an e-mail message template. '$nickname', '$sitename', and '$userpass' can be used in the template for substitution. $MAILSUBJECT is the subject line for said email. To skip sending an email on creation, leave $MAILTEMPLATE blank or point to a non-existing file. --- scripts/sendemail.php | 82 +++++++++++++++++++++++++++++++++++++++++ scripts/setup.cfg.sample | 5 ++- scripts/setup_status_network.sh | 50 +++++++++++++++++++++++-- 3 files changed, 132 insertions(+), 5 deletions(-) create mode 100755 scripts/sendemail.php diff --git a/scripts/sendemail.php b/scripts/sendemail.php new file mode 100755 index 000000000..436e085be --- /dev/null +++ b/scripts/sendemail.php @@ -0,0 +1,82 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'i:n:'; +$longoptions = array('id=', 'nickname=', 'subject='); + +$helptext = << +Sends given email text to user. + + -i --id id of the user to query + -n --nickname nickname of the user to query + --subject mail subject line (required) + +END_OF_USEREMAIL_HELP; + +require_once INSTALLDIR.'/scripts/commandline.inc'; + +if (have_option('i', 'id')) { + $id = get_option_value('i', 'id'); + $user = User::staticGet('id', $id); + if (empty($user)) { + print "Can't find user with ID $id\n"; + exit(1); + } +} else if (have_option('n', 'nickname')) { + $nickname = get_option_value('n', 'nickname'); + $user = User::staticGet('nickname', $nickname); + if (empty($user)) { + print "Can't find user with nickname '$nickname'\n"; + exit(1); + } +} else { + print "You must provide a user by --id or --nickname\n"; + exit(1); +} + +if (empty($user->email)) { + // @fixme unconfirmed address? + print "No email registered for user '$user->nickname'\n"; + exit(1); +} + +if (!have_option('subject')) { + echo "You must provide a subject line for the mail in --subject='...' param.\n"; + exit(1); +} +$subject = get_option_value('subject'); + +if (posix_isatty(STDIN)) { + print "You must provide message input on stdin!\n"; + exit(1); +} +$body = file_get_contents('php://stdin'); + +print "Sending to $user->email..."; +if (mail_to_user($user, $subject, $body)) { + print " done\n"; +} else { + print " failed.\n"; + exit(1); +} + diff --git a/scripts/setup.cfg.sample b/scripts/setup.cfg.sample index 8d03b06f5..a0f10b352 100644 --- a/scripts/setup.cfg.sample +++ b/scripts/setup.cfg.sample @@ -11,4 +11,7 @@ export AVATARBASE=/var/www/avatar.example.net export BACKGROUNDBASE=/var/www/background.example.net export FILEBASE=/var/www/file.example.net export PWDGEN="pwgen 20" - +export PHPBASE=/var/www/statusnet +export WILDCARD=example.net +export MAILTEMPLATE=/etc/statusnet/newsite-mail.txt +export MAILSUBJECT="Your new StatusNet site" diff --git a/scripts/setup_status_network.sh b/scripts/setup_status_network.sh index 777711fb5..d468df3ae 100755 --- a/scripts/setup_status_network.sh +++ b/scripts/setup_status_network.sh @@ -2,9 +2,22 @@ source /etc/statusnet/setup.cfg -export nickname=$1 -export sitename=$2 +# setup_status_net.sh mysite 'My Site' 'owner@example.com' '1user' +export nickname="$1" +export sitename="$2" +export email="$3" +export tags="$4" + +# Fixme: if this is changed later we need to update profile URLs +# for the created user. +export server="$nickname.$WILDCARD" + +# End-user info +export userpass=`$PWDGEN` +export roles="administrator moderator owner" + +# DB info export password=`$PWDGEN` export database=$nickname$DBBASE export username=$nickname$USERBASE @@ -21,8 +34,8 @@ mysql -h $DBHOST -u $ADMIN --password=$ADMINPASS $SITEDB << ENDOFCOMMANDS GRANT ALL ON $database.* TO '$username'@'localhost' IDENTIFIED BY '$password'; GRANT ALL ON $database.* TO '$username'@'%' IDENTIFIED BY '$password'; -INSERT INTO status_network (nickname, dbhost, dbuser, dbpass, dbname, sitename, created) -VALUES ('$nickname', '$DBHOSTNAME', '$username', '$password', '$database', '$sitename', now()); +INSERT INTO status_network (nickname, dbhost, dbuser, dbpass, dbname, sitename, created, tags) +VALUES ('$nickname', '$DBHOSTNAME', '$username', '$password', '$database', '$sitename', now(), '$tags'); ENDOFCOMMANDS @@ -30,3 +43,32 @@ for top in $AVATARBASE $FILEBASE $BACKGROUNDBASE; do mkdir $top/$nickname chmod a+w $top/$nickname done + +php $PHPBASE/scripts/registeruser.php \ + -s"$server" \ + -n"$nickname" \ + -w"$userpass" \ + -e"$email" + +for role in $roles +do + php $PHPBASE/scripts/userrole.php \ + -s"$server" \ + -n"$nickname" \ + -r"$role" +done + +if [ -f "$MAILTEMPLATE" ] +then + # fixme how safe is this? are sitenames sanitized? + cat $MAILTEMPLATE | \ + sed "s/\$nickname/$nickname/" | \ + sed "s/\$sitename/$sitename/" | \ + sed "s/\$userpass/$userpass/" | \ + php $PHPBASE/scripts/sendemail.php \ + -s"$server" \ + -n"$nickname" \ + --subject="$MAILSUBJECT" +else + echo "No mail template, not sending email." +fi -- cgit v1.2.3-54-g00ecf From 0373ab6fa437309ab790d5de4d43e241ba510c93 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 18:35:02 -0500 Subject: Plugin to enable OpenX ads --- plugins/OpenX/OpenXPlugin.php | 166 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 plugins/OpenX/OpenXPlugin.php diff --git a/plugins/OpenX/OpenXPlugin.php b/plugins/OpenX/OpenXPlugin.php new file mode 100644 index 000000000..a0d02ec11 --- /dev/null +++ b/plugins/OpenX/OpenXPlugin.php @@ -0,0 +1,166 @@ +. + * + * @category Ads + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +$_OpenXPlugin_Script = <<<\/scr"+"ipt>"); +ENDOFSCRIPT; + +/** + * Plugin for OpenX Ad Server + * + * This plugin supports the OpenX ad server, http://www.openx.org/ + * + * We support the 4 ad sizes for the Universal Ad Platform (UAP): + * + * Medium Rectangle + * (Small) Rectangle + * Leaderboard + * Wide Skyscraper + * + * They fit in different places on the default theme. Some themes + * might interact quite poorly with this plugin. + * + * To enable advertising, you will need an OpenX server. You'll need + * to set up a "zone" for your StatusNet site that identifies a + * kind of ad you want to place (of the above 4 sizes). + * + * Add the plugin to config.php like so: + * + * addPlugin('OpenX', array('adScript' => 'full path to script', + * 'rectangle' => 1)); + * + * Here, the 'adScript' parameter is the full path to the OpenX + * ad script, like 'http://example.com/www/delivery/ajs.php'. Note + * that we don't do any magic to swap between HTTP and HTTPS, so + * if you want HTTPS, say so. + * + * The 'rectangle' parameter is the zone ID for that ad space on + * your site. If you've configured another size, try 'mediumRectangle', + * 'leaderboard', or 'wideSkyscraper'. + * + * If for some reason your ad server is different from the default, + * use the 'adScript' parameter to set the full path to the ad script. + * + * @category Ads + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @seeAlso UAPPlugin + */ + +class OpenXPlugin extends UAPPlugin +{ + public $adScript = null; + + /** + * Show a medium rectangle 'ad' + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showMediumRectangle($action) + { + $this->showAd($action, $this->mediumRectangle); + } + + /** + * Show a rectangle 'ad' + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showRectangle($action) + { + $this->showAd($action, $this->rectangle); + } + + /** + * Show a wide skyscraper ad + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showWideSkyscraper($action) + { + $this->showAd($action, $this->wideSkyscraper); + } + + /** + * Show a leaderboard ad + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showLeaderboard($action) + { + $this->showAd($action, $this->leaderboard); + } + + /** + * Show an ad using OpenX + * + * @param integer $zone Zone to show + * + * @return void + */ + + protected function showAd($zone) + { + global $_OpenXPlugin_Script; + + $this->inlineScript(sprintf($_OpenXPlugin_Script, $this->adScript, $zone)); + return true; + } +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf From e7a5471287598902d4fefbb93f1247d8c025e66b Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 15:37:18 -0800 Subject: add additional post-install shell script option for setup_status_network.sh to do any other site-specific setup --- scripts/setup.cfg.sample | 1 + scripts/setup_status_network.sh | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/scripts/setup.cfg.sample b/scripts/setup.cfg.sample index a0f10b352..f247a3bca 100644 --- a/scripts/setup.cfg.sample +++ b/scripts/setup.cfg.sample @@ -15,3 +15,4 @@ export PHPBASE=/var/www/statusnet export WILDCARD=example.net export MAILTEMPLATE=/etc/statusnet/newsite-mail.txt export MAILSUBJECT="Your new StatusNet site" +export POSTINSTALL=/etc/statusnet/morestuff.sh diff --git a/scripts/setup_status_network.sh b/scripts/setup_status_network.sh index d468df3ae..ae392191f 100755 --- a/scripts/setup_status_network.sh +++ b/scripts/setup_status_network.sh @@ -72,3 +72,9 @@ then else echo "No mail template, not sending email." fi + +if [ -f "$POSTINSTALL" ] +then + echo "Running $POSTINSTALL ..." + source "$POSTINSTALL" +fi -- cgit v1.2.3-54-g00ecf From 97a1ef14d2bfd32ec14db57b56d861e9cad7d02d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 18:39:55 -0500 Subject: using an action for output in OpenX plugin --- plugins/OpenX/OpenXPlugin.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/OpenX/OpenXPlugin.php b/plugins/OpenX/OpenXPlugin.php index a0d02ec11..96ed82b44 100644 --- a/plugins/OpenX/OpenXPlugin.php +++ b/plugins/OpenX/OpenXPlugin.php @@ -151,16 +151,17 @@ class OpenXPlugin extends UAPPlugin /** * Show an ad using OpenX * - * @param integer $zone Zone to show + * @param Action $action Action being shown + * @param integer $zone Zone to show * * @return void */ - protected function showAd($zone) + protected function showAd($action, $zone) { global $_OpenXPlugin_Script; - $this->inlineScript(sprintf($_OpenXPlugin_Script, $this->adScript, $zone)); + $action->inlineScript(sprintf($_OpenXPlugin_Script, $this->adScript, $zone)); return true; } } \ No newline at end of file -- cgit v1.2.3-54-g00ecf From dd413ff4faeada13571711a0e4cc51e5ab25f7cc Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 18:44:46 -0500 Subject: move script into OpenXPlugin::showAd() so it works --- plugins/OpenX/OpenXPlugin.php | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/plugins/OpenX/OpenXPlugin.php b/plugins/OpenX/OpenXPlugin.php index 96ed82b44..59485f25d 100644 --- a/plugins/OpenX/OpenXPlugin.php +++ b/plugins/OpenX/OpenXPlugin.php @@ -31,22 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -$_OpenXPlugin_Script = <<<\/scr"+"ipt>"); -ENDOFSCRIPT; - /** * Plugin for OpenX Ad Server * @@ -159,9 +143,23 @@ class OpenXPlugin extends UAPPlugin protected function showAd($action, $zone) { - global $_OpenXPlugin_Script; +$scr = <<<\/scr"+"ipt>"); +ENDOFSCRIPT; - $action->inlineScript(sprintf($_OpenXPlugin_Script, $this->adScript, $zone)); + $action->inlineScript(sprintf($scr, $this->adScript, $zone)); return true; } } \ No newline at end of file -- cgit v1.2.3-54-g00ecf From fe531dfc6c9c700b5566db634a9078d4b4b87d87 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 16:03:01 -0800 Subject: Shuffle params on setup_status_network; adding fullname and pushing tags up --- scripts/setup_status_network.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/setup_status_network.sh b/scripts/setup_status_network.sh index ae392191f..f502a169a 100755 --- a/scripts/setup_status_network.sh +++ b/scripts/setup_status_network.sh @@ -2,12 +2,13 @@ source /etc/statusnet/setup.cfg -# setup_status_net.sh mysite 'My Site' 'owner@example.com' '1user' +# setup_status_net.sh mysite 'My Site' '1user' 'owner@example.com' 'Firsty McLastname' export nickname="$1" export sitename="$2" -export email="$3" -export tags="$4" +export tags="$3" +export email="$4" +export fullname="$5" # Fixme: if this is changed later we need to update profile URLs # for the created user. @@ -47,6 +48,7 @@ done php $PHPBASE/scripts/registeruser.php \ -s"$server" \ -n"$nickname" \ + -f"$fullname" \ -w"$userpass" \ -e"$email" -- cgit v1.2.3-54-g00ecf From 420ae06faf033b799c677845045b50b259801dbd Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 28 Jan 2010 00:39:40 +0000 Subject: These API methods should return true for ->isReadOnly($args)! --- actions/apiaccountratelimitstatus.php | 17 ++++++++++++++++- actions/apifriendshipsexists.php | 15 +++++++++++++++ actions/apifriendshipsshow.php | 16 +++++++++++++++- actions/apigroupismember.php | 15 +++++++++++++++ actions/apigroupshow.php | 15 +++++++++++++++ actions/apihelptest.php | 15 +++++++++++++++ actions/apistatusnetconfig.php | 15 +++++++++++++++ actions/apistatusnetversion.php | 15 +++++++++++++++ actions/apiusershow.php | 15 +++++++++++++++ 9 files changed, 136 insertions(+), 2 deletions(-) diff --git a/actions/apiaccountratelimitstatus.php b/actions/apiaccountratelimitstatus.php index 1a5afd552..f19e315bf 100644 --- a/actions/apiaccountratelimitstatus.php +++ b/actions/apiaccountratelimitstatus.php @@ -105,7 +105,22 @@ class ApiAccountRateLimitStatusAction extends ApiBareAuthAction print json_encode($out); } - $this->endDocument($this->format); + $this->endDocument($this->format); + } + + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + return true; } } diff --git a/actions/apifriendshipsexists.php b/actions/apifriendshipsexists.php index c040b9f6a..ca62b5f51 100644 --- a/actions/apifriendshipsexists.php +++ b/actions/apifriendshipsexists.php @@ -116,4 +116,19 @@ class ApiFriendshipsExistsAction extends ApiPrivateAuthAction } } + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + return true; + } + } diff --git a/actions/apifriendshipsshow.php b/actions/apifriendshipsshow.php index 73ecc9249..f29e63713 100644 --- a/actions/apifriendshipsshow.php +++ b/actions/apifriendshipsshow.php @@ -87,7 +87,6 @@ class ApiFriendshipsShowAction extends ApiBareAuthAction return true; } - /** * Determines whether this API resource requires auth. Overloaded to look * return true in case source_id and source_screen_name are both empty @@ -165,4 +164,19 @@ class ApiFriendshipsShowAction extends ApiBareAuthAction } + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + return true; + } + } diff --git a/actions/apigroupismember.php b/actions/apigroupismember.php index 69ead0b53..97f843561 100644 --- a/actions/apigroupismember.php +++ b/actions/apigroupismember.php @@ -119,4 +119,19 @@ class ApiGroupIsMemberAction extends ApiBareAuthAction } } + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + return true; + } + } diff --git a/actions/apigroupshow.php b/actions/apigroupshow.php index 7aa49b1bf..95d6f95af 100644 --- a/actions/apigroupshow.php +++ b/actions/apigroupshow.php @@ -149,4 +149,19 @@ class ApiGroupShowAction extends ApiPrivateAuthAction return null; } + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + return true; + } + } diff --git a/actions/apihelptest.php b/actions/apihelptest.php index 7b4017531..d0e9e4926 100644 --- a/actions/apihelptest.php +++ b/actions/apihelptest.php @@ -92,5 +92,20 @@ class ApiHelpTestAction extends ApiPrivateAuthAction } } + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + return true; + } + } diff --git a/actions/apistatusnetconfig.php b/actions/apistatusnetconfig.php index ab96f2e5f..dc1ab8685 100644 --- a/actions/apistatusnetconfig.php +++ b/actions/apistatusnetconfig.php @@ -138,5 +138,20 @@ class ApiStatusnetConfigAction extends ApiAction } } + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + return true; + } + } diff --git a/actions/apistatusnetversion.php b/actions/apistatusnetversion.php index 5109cd806..d09480759 100644 --- a/actions/apistatusnetversion.php +++ b/actions/apistatusnetversion.php @@ -98,5 +98,20 @@ class ApiStatusnetVersionAction extends ApiPrivateAuthAction } } + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + return true; + } + } diff --git a/actions/apiusershow.php b/actions/apiusershow.php index a7fe0dcc1..6c8fad49b 100644 --- a/actions/apiusershow.php +++ b/actions/apiusershow.php @@ -123,4 +123,19 @@ class ApiUserShowAction extends ApiPrivateAuthAction } + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + return true; + } + } -- cgit v1.2.3-54-g00ecf From 324590c46eba2900164d0487a2d525ea4e9f93b8 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 28 Jan 2010 00:41:44 +0000 Subject: Some adjustments to the way API auth works after merging testing and 0.9.x --- lib/apiauth.php | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/apiauth.php b/lib/apiauth.php index ac5e997c7..d441014ad 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -57,7 +57,6 @@ class ApiAuthAction extends ApiAction var $auth_user_password = null; var $access_token = null; var $oauth_source = null; - var $auth_user = null; /** * Take arguments for running, and output basic auth header if needed @@ -82,22 +81,27 @@ class ApiAuthAction extends ApiAction if (!empty($this->access_token)) { $this->checkOAuthRequest(); } else { - $this->checkBasicAuthUser(); + $this->checkBasicAuthUser(true); } } else { // Check to see if a basic auth user is there even // if one's not required - $this->checkBasicAuthUser(false); + if (empty($this->access_token)) { + $this->checkBasicAuthUser(false); + } } // Reject API calls with the wrong access level if ($this->isReadOnly($args) == false) { + + common_debug(get_class($this) . ' is not read-only!'); + if ($this->access != self::READ_WRITE) { - $msg = 'API resource requires read-write access, ' . - 'but you only have read access.'; + $msg = _('API resource requires read-write access, ' . + 'but you only have read access.'); $this->clientError($msg, 401, $this->format); exit; } @@ -176,7 +180,7 @@ class ApiAuthAction extends ApiAction ($this->access = self::READ_WRITE) ? 'read-write' : 'read-only' )); - return true; + return; } else { throw new OAuthException('Bad access token.'); } @@ -228,9 +232,14 @@ class ApiAuthAction extends ApiAction } else { + $user = common_check_user($this->auth_user_nickname, + $this->auth_user_password); + if (Event::handle('StartSetApiUser', array(&$user))) { - $this->auth_user = common_check_user($this->auth_user_nickname, - $this->auth_user_password); + + if (!empty($user)) { + $this->auth_user = $user; + } Event::handle('EndSetApiUser', array($user)); } @@ -239,18 +248,18 @@ class ApiAuthAction extends ApiAction $this->access = self::READ_WRITE; - if (empty($this->auth_user)) { + if (empty($this->auth_user) && $required) { // basic authentication failed list($proxy, $ip) = common_client_ip(); - common_log( - LOG_WARNING, - 'Failed API auth attempt, nickname = ' . - "$nickname, proxy = $proxy, ip = $ip." - ); - + $msg = sprintf(_('Failed API auth attempt, nickname = %1$s, ' . + 'proxy = %2$s, ip = %3$s'), + $this->auth_user_nickname, + $proxy, + $ip); + common_log(LOG_WARNING, $msg); $this->showAuthError(); exit; } -- cgit v1.2.3-54-g00ecf From f296f04abdc545f03daf55676282a2ae7108f79e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 28 Jan 2010 01:24:40 +0000 Subject: Remove debugging statement --- lib/apiauth.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/apiauth.php b/lib/apiauth.php index d441014ad..c684a6cae 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -96,9 +96,6 @@ class ApiAuthAction extends ApiAction // Reject API calls with the wrong access level if ($this->isReadOnly($args) == false) { - - common_debug(get_class($this) . ' is not read-only!'); - if ($this->access != self::READ_WRITE) { $msg = _('API resource requires read-write access, ' . 'but you only have read access.'); -- cgit v1.2.3-54-g00ecf From 07d50a012af70d9bd35a26f645d791ae7ba29986 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 17:34:13 -0800 Subject: fix update script -- read the diff wrong and put a couple fields on wrong table (whoops) --- db/rc3torc4.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/db/rc3torc4.sql b/db/rc3torc4.sql index 8342c4bc6..917c1f1c4 100644 --- a/db/rc3torc4.sql +++ b/db/rc3torc4.sql @@ -15,7 +15,9 @@ alter table queue_item rename to queue_item_old; alter table queue_item_new rename to queue_item; alter table consumer - add consumer_secret varchar(255) not null comment 'secret value', + add consumer_secret varchar(255) not null comment 'secret value'; + +alter table token add verifier varchar(255) comment 'verifier string for OAuth 1.0a', add verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a'; -- cgit v1.2.3-54-g00ecf From 5182cc686dacfcf64130b591a3b7ded8d4a0c9dc Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 28 Jan 2010 01:39:18 +0000 Subject: Numbered format specifiers --- actions/apioauthauthorize.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index fa074c4e7..15c3a9dad 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -303,8 +303,9 @@ class ApiOauthAuthorizeAction extends ApiOauthAction $access = ($this->app->access_type & Oauth_application::$writeAccess) ? 'access and update' : 'access'; - $msg = _("The application %s by %s would like " . - "the ability to %s your account data."); + $msg = _('The application %1$s by ' . + '%2$s would like the ability ' . + 'to %3$s your account data.'); $this->raw(sprintf($msg, $this->app->name, -- cgit v1.2.3-54-g00ecf From 3abfb454a381806a0f237e1463d2ba9a8612c22a Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 18:39:17 -0800 Subject: Adds an emergency switch so we can run inbox distribution at save time (bypassing 'distrib' queue) Set $config['queue']['inboxes'] = false to do so --- classes/Notice.php | 10 +++++++--- lib/default.php | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 0966697e2..6b364fb5c 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -326,9 +326,13 @@ class Notice extends Memcached_DataObject # XXX: someone clever could prepend instead of clearing the cache $notice->blowOnInsert(); - $qm = QueueManager::get(); - - $qm->enqueue($notice, 'distrib'); + if (common_config('queue', 'inboxes')) { + $qm = QueueManager::get(); + $qm->enqueue($notice, 'distrib'); + } else { + $handler = new DistribQueueHandler(); + $handler->handle($notice); + } return $notice; } diff --git a/lib/default.php b/lib/default.php index 10ea34864..c729193b5 100644 --- a/lib/default.php +++ b/lib/default.php @@ -87,6 +87,7 @@ $default = 'monitor' => null, // URL to monitor ping endpoint (work in progress) 'softlimit' => '90%', // total size or % of memory_limit at which to restart queue threads gracefully 'debug_memory' => false, // true to spit memory usage to log + 'inboxes' => true, // true to do inbox distribution & output queueing from in background via 'distrib' queue ), 'license' => array('type' => 'cc', # can be 'cc', 'allrightsreserved', 'private' -- cgit v1.2.3-54-g00ecf From c805a5e51874aa231c728c14402542648edb24bf Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 18:55:22 -0800 Subject: Update queue, translation notes in readme --- README | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/README b/README index f83873ca8..da278f741 100644 --- a/README +++ b/README @@ -2,8 +2,8 @@ README ------ -StatusNet 0.9.0 ("Stand") Beta 3 -20 Jan 2010 +StatusNet 0.9.0 ("Stand") Beta 4 +27 Jan 2010 This is the README file for StatusNet (formerly Laconica), the Open Source microblogging platform. It includes installation instructions, @@ -597,26 +597,19 @@ server is probably a good idea for high-volume sites. needs as a parameter the install path; if you run it from the StatusNet dir, "." should suffice. -This will run eight (for now) queue handlers: +This will run the queue handlers: +* queuedaemon.php - polls for queued items for inbox processing and + pushing out to OMB, SMS, XMPP, etc. * xmppdaemon.php - listens for new XMPP messages from users and stores - them as notices in the database. -* jabberqueuehandler.php - sends queued notices in the database to - registered users who should receive them. -* publicqueuehandler.php - sends queued notices in the database to - public feed listeners. -* ombqueuehandler.php - sends queued notices to OpenMicroBlogging - recipients on foreign servers. -* smsqueuehandler.php - sends queued notices to SMS-over-email addresses - of registered users. -* xmppconfirmhandler.php - sends confirmation messages to registered - users. - -Note that these queue daemons are pretty raw, and need your care. In -particular, they leak memory, and you may want to restart them on a -regular (daily or so) basis with a cron job. Also, if they lose -the connection to the XMPP server for too long, they'll simply die. It -may be a good idea to use a daemon-monitoring service, like 'monit', + them as notices in the database; also pulls queued XMPP output from + queuedaemon.php to push out to clients. + +These two daemons will automatically restart in most cases of failure +including memory leaks (if a memory_limit is set), but may still die +or behave oddly if they lose connections to the XMPP or queue servers. + +It may be a good idea to use a daemon-monitoring service, like 'monit', to check their status and keep them running. All the daemons write their process IDs (pids) to /var/run/ by @@ -626,7 +619,7 @@ daemons. Since version 0.8.0, it's now possible to use a STOMP server instead of our kind of hacky home-grown DB-based queue solution. See the "queues" config section below for how to configure to use STOMP. As of this -writing, the software has been tested with ActiveMQ ( +writing, the software has been tested with ActiveMQ. Sitemaps -------- @@ -712,10 +705,12 @@ subdirectory to add a new language to your system. You'll need to compile the ".po" files into ".mo" files, however. Contributions of translation information to StatusNet are very easy: -you can use the Web interface at http://status.net/pootle/ to add one +you can use the Web interface at TranslateWiki.net to add one or a few or lots of new translations -- or even new languages. You can also download more up-to-date .po files there, if you so desire. +For info on helping with translations, see http://status.net/wiki/Translations + Backups ------- -- cgit v1.2.3-54-g00ecf From ee4ea3f3e1eb64df2dd3ba677f5201f8787482a8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 21:59:38 -0500 Subject: increment software beta version --- lib/common.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common.php b/lib/common.php index ada48b339..b4e4a653c 100644 --- a/lib/common.php +++ b/lib/common.php @@ -22,7 +22,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } //exit with 200 response, if this is checking fancy from the installer if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; } -define('STATUSNET_VERSION', '0.9.0beta3'); +define('STATUSNET_VERSION', '0.9.0beta4'); define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility define('STATUSNET_CODENAME', 'Stand'); -- cgit v1.2.3-54-g00ecf