diff options
Diffstat (limited to 'actions')
-rw-r--r-- | actions/apiaccountverifycredentials.php | 14 | ||||
-rw-r--r-- | actions/apioauthaccesstoken.php | 94 | ||||
-rw-r--r-- | actions/apioauthauthorize.php | 376 | ||||
-rw-r--r-- | actions/apioauthrequesttoken.php | 99 | ||||
-rw-r--r-- | actions/apistatusesupdate.php | 5 | ||||
-rw-r--r-- | actions/editapplication.php | 264 | ||||
-rw-r--r-- | actions/newapplication.php | 277 | ||||
-rw-r--r-- | actions/oauthappssettings.php | 166 | ||||
-rw-r--r-- | actions/oauthconnectionssettings.php | 212 | ||||
-rw-r--r-- | actions/showapplication.php | 328 |
10 files changed, 1835 insertions, 0 deletions
diff --git a/actions/apiaccountverifycredentials.php b/actions/apiaccountverifycredentials.php index 08b201dbf..1095d5162 100644 --- a/actions/apiaccountverifycredentials.php +++ b/actions/apiaccountverifycredentials.php @@ -82,4 +82,18 @@ class ApiAccountVerifyCredentialsAction extends ApiAuthAction } + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + * + **/ + + function isReadOnly($args) + { + return true; + } + } diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php new file mode 100644 index 000000000..085ef6f0b --- /dev/null +++ b/actions/apioauthaccesstoken.php @@ -0,0 +1,94 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Exchange an authorized OAuth request token for an access token + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apioauth.php'; + +/** + * Exchange an authorized OAuth request token for an access token + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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 ApiOauthAction +{ + + /** + * 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.'); + 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"; + } +} + diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php new file mode 100644 index 000000000..fa074c4e7 --- /dev/null +++ b/actions/apioauthauthorize.php @@ -0,0 +1,376 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Authorize an OAuth request token + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apioauth.php'; + +/** + * Authorize an OAuth request token + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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 ApiOauthAction +{ + 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("apioauthauthorize"); + + $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(); + $this->app = $this->store->getAppByRequestToken($this->oauth_token); + + 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); + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + + $this->handlePost(); + + } else { + + // XXX: make better error messages + + if (empty($this->oauth_token)) { + + common_debug("No request token found."); + + $this->clientError(_('Bad request.')); + return; + } + + if (empty($this->app)) { + common_debug('No app for that token.'); + $this->clientError(_('Bad request.')); + return; + } + + $name = $this->app->name; + common_debug("Requesting auth for app: " . $name); + + $this->showForm(); + } + } + + function handlePost() + { + common_debug("handlePost()"); + + // check 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; + } + + // 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 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(); + + if (!$result) { + common_log_db_error($appUser, 'DELETE', __FILE__); + throw new ServerException(_('DB error deleting OAuth app user.')); + return; + } + } + + // associated the authorized req token with the user and the app + + $appUser = new Oauth_application_user(); + + $appUser->profile_id = $user->id; + $appUser->application_id = $this->app->id; + + // 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->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 + + // 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. + + $target_url = $this->getCallback($this->callback, + array('oauth_token' => $this->oauth_token)); + + common_debug("Doing callback to $target_url"); + + 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 + + $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(); + if (!common_logged_in()) { + $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'); + } + + /** + * Shows the authorization form. + * + * @return void + */ + + function showContent() + { + $this->elementStart('form', array('method' => 'post', + '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('ul', 'form_data'); + $this->elementStart('li'); + $this->elementStart('p'); + if (!empty($this->app->icon)) { + $this->element('img', array('src' => $this->app->icon)); + } + + $access = ($this->app->access_type & Oauth_application::$writeAccess) ? + 'access and update' : 'access'; + + $msg = _("The application <strong>%s</strong> by <strong>%s</strong> would like " . + "the ability to <strong>%s</strong> your account data."); + + $this->raw(sprintf($msg, + $this->app->name, + $this->app->organization, + $access)); + $this->elementEnd('p'); + $this->elementEnd('li'); + $this->elementEnd('ul'); + + if (!common_logged_in()) { + + $this->elementStart('fieldset'); + $this->element('legend', null, _('Account')); + $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 submit form_action-primary', + 'name' => 'deny', + 'type' => 'submit', + 'value' => _('Deny'))); + + $this->element('input', array('id' => 'allow_submit', + 'class' => 'submit submit form_action-secondary', + 'name' => 'allow', + 'type' => 'submit', + 'value' => _('Allow'))); + + $this->elementEnd('fieldset'); + $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 new file mode 100644 index 000000000..467640b9a --- /dev/null +++ b/actions/apioauthrequesttoken.php @@ -0,0 +1,99 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Get an OAuth request token + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apioauth.php'; + +/** + * Get an OAuth request token + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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 ApiOauthAction +{ + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->callback = $this->arg('oauth_callback'); + + if (!empty($this->callback)) { + common_debug("callback: $this->callback"); + } + + return true; + } + + /** + * 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, '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/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/actions/editapplication.php b/actions/editapplication.php new file mode 100644 index 000000000..9cc3e3cea --- /dev/null +++ b/actions/editapplication.php @@ -0,0 +1,264 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Edit an OAuth Application + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Applications + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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 <zach@status.net> + * @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 $owner = 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); + $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.')); + return false; + } + + return true; + } + + /** + * Handle the request + * + * On GET, show the form. On POST, try to save the app. + * + * @param array $args unused + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $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('showapplication', + array('id' => $this->app->id)), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } + } + + 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('default_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 (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; + } 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 ((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)); + + $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; + + common_debug("access_type = $access_type"); + + if ($access_type == 'r') { + $this->app->access_type = 1; + } else { + $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('oauthappssettings'), 303); + } + +} + diff --git a/actions/newapplication.php b/actions/newapplication.php new file mode 100644 index 000000000..c499fe7c7 --- /dev/null +++ b/actions/newapplication.php @@ -0,0 +1,277 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Register a new OAuth Application + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Applications + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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 <zach@status.net> + * @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 OwnerDesignAction +{ + 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 register an application.')); + return false; + } + + return true; + } + + /** + * Handle the request + * + * On GET, show the form. On POST, try to save the app. + * + * @param array $args unused + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $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('oauthappssettings'), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } + } + + 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('homepage'); + $callback_url = $this->trimmed('callback_url'); + $type = $this->arg('app_type'); + $access_type = $this->arg('default_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 (mb_strlen($callback_url) > 255) { + $this->showForm(_('Callback is too long.')); + 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)); + + $app = new Oauth_application(); + + $app->query('BEGIN'); + + $app->name = $name; + $app->owner = $cur->id; + $app->description = $description; + $app->source_url = $source_url; + $app->organization = $organization; + $app->homepage = $homepage; + $app->callback_url = $callback_url; + $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 + + $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; + + $this->app_id = $app->insert(); + + if (!$this->app_id) { + common_log_db_error($app, 'INSERT', __FILE__); + $this->serverError(_('Could not create application.')); + $app->query('ROLLBACK'); + } + + $app->uploadLogo(); + + $app->query('COMMIT'); + + 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 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * List the OAuth applications that a user has registered with this instance + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Settings + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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 <zach@status.net> + * @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 new file mode 100644 index 000000000..b17729b82 --- /dev/null +++ b/actions/oauthconnectionssettings.php @@ -0,0 +1,212 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * List a user's OAuth connected applications + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Settings + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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 <zach@status.net> + * @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 +{ + + 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; + } + + /** + * Title of the page + * + * @return string Title of the page + */ + + function title() + { + return _('Connected Applications'); + } + + function isReadOnly($args) + { + return true; + } + + /** + * 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); + + $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', + 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; + } + + 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() + { + $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'); + } + + 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('oauthappssettings')), + 'here.'); + $this->elementEnd('p'); + } + +} diff --git a/actions/showapplication.php b/actions/showapplication.php new file mode 100644 index 000000000..049206375 --- /dev/null +++ b/actions/showapplication.php @@ -0,0 +1,328 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show an OAuth application + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Application + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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 <zach@status.net> + * @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 false; + } + + 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(); + + $consumer = $this->application->getConsumer(); + + $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, + 'class' => 'photo logo')); + } + $this->elementEnd('dd'); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_fn'); + $this->element('dt', null, _('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->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'); + $this->element('dt', null, _('Description')); + $this->element('dd', 'note', $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); + + $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'), + $profile->getBestName(), + $defaultAccess, + $userCnt + )); + $this->elementEnd('dd'); + $this->elementEnd('dl'); + $this->elementEnd('div'); + + $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('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('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')); + $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', null, $consumer->consumer_secret); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_request_token_url'); + $this->element('dt', null, _('Request token URL')); + $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', null, common_local_url('apioauthaccesstoken')); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_authorize_url'); + $this->element('dt', null, _('Authorize URL')); + $this->element('dd', null, common_local_url('apioauthauthorize')); + $this->elementEnd('dl'); + + $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')); + $this->element('a', + array('href' => common_local_url('oauthappssettings'), + 'class' => 'more'), + 'View your applications'); + $this->elementEnd('p'); + } + + 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(); + } + +} + |