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 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'actions') 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. '. -- 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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 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 (limited to 'actions') 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 (limited to 'actions') 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 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 (limited to 'actions') 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(+) (limited to 'actions') 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(-) (limited to 'actions') 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 (limited to 'actions') 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 (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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 (limited to 'actions') 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(-) (limited to 'actions') 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 (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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 (limited to 'actions') 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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 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(-) (limited to 'actions') 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