Merge branch 'testing' of git@gitorious.org:statusnet/mainline into testing

This commit is contained in:
Evan Prodromou 2010-01-24 22:42:29 -05:00
commit 536170d788
33 changed files with 3390 additions and 16 deletions

View File

@ -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;
}
}

View File

@ -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";
}
}

View File

@ -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()
{
}
}

View File

@ -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";
}
}
}

View File

@ -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';
}

264
actions/editapplication.php Normal file
View File

@ -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);
}
}

277
actions/newapplication.php Normal file
View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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');
}
}

328
actions/showapplication.php Normal file
View File

@ -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();
}
}

View File

@ -4,16 +4,17 @@
*/
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 */
public $__table = 'consumer'; // table name
public $consumer_key; // varchar(255) primary_key not_null
public $consumer_secret; // varchar(255) not_null
public $seed; // char(32) not_null
public $created; // datetime() not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
public $created; // datetime not_null
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
/* Static get */
function staticGet($k,$v=null)
@ -21,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;
}
}

View File

@ -0,0 +1,140 @@
<?php
/**
* Table Definition for oauth_application
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Oauth_application extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'oauth_application'; // table name
public $id; // int(4) primary_key not_null
public $owner; // int(4) not_null
public $consumer_key; // varchar(255) not_null
public $name; // varchar(255) not_null
public $description; // varchar(255)
public $icon; // varchar(255) not_null
public $source_url; // varchar(255)
public $organization; // varchar(255)
public $homepage; // varchar(255)
public $callback_url; // varchar(255) not_null
public $type; // tinyint(1)
public $access_type; // tinyint(1)
public $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',$k,$v);
}
/* 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;
}
}
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);
}
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;
}
/**
* Handle an image upload
*
* Does all the magic for handling an image upload, and crops the
* image by default.
*
* @return void
*/
function uploadLogo()
{
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($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);
}
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Table Definition for oauth_application_user
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Oauth_application_user extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'oauth_application_user'; // table name
public $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 $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);
}
/* 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;
}
}

View File

@ -352,6 +352,31 @@ class Profile extends Memcached_DataObject
return $profile;
}
function getApplications($offset = 0, $limit = null)
{
$qry =
'SELECT a.* ' .
'FROM oauth_application_user u, oauth_application a ' .
'WHERE u.profile_id = %d ' .
'AND a.id = u.application_id ' .
'AND u.access_type > 0 ' .
'ORDER BY u.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();

View File

@ -4,7 +4,7 @@
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Token extends Memcached_DataObject
class Token extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
@ -14,7 +14,9 @@ class Token extends Memcached_DataObject
public $tok; // char(32) primary_key not_null
public $secret; // char(32) not_null
public $type; // tinyint(1) not_null
public $state; // tinyint(1)
public $state; // tinyint(1)
public $verifier; // varchar(255)
public $verified_callback; // varchar(255)
public $created; // datetime() not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP

View File

@ -39,6 +39,7 @@ code = K
[consumer]
consumer_key = 130
consumer_secret = 130
seed = 130
created = 142
modified = 384
@ -348,6 +349,37 @@ created = 142
tag = K
notice_id = K
[oauth_application]
id = 129
owner = 129
consumer_key = 130
name = 130
description = 2
icon = 130
source_url = 2
organization = 2
homepage = 2
callback_url = 130
type = 17
access_type = 17
created = 142
modified = 384
[oauth_application__keys]
id = N
[oauth_application_user]
profile_id = 129
application_id = 129
access_type = 17
token = 2
created = 142
modified = 384
[oauth_application_user__keys]
profile_id = K
application_id = K
[profile]
id = 129
nickname = 130
@ -484,6 +516,8 @@ tok = 130
secret = 130
type = 145
state = 17
verifier = 2
verified_callback = 2
created = 142
modified = 384

View File

@ -176,6 +176,7 @@ create table fave (
create table consumer (
consumer_key varchar(255) primary key comment 'unique identifier, root URL',
consumer_secret varchar(255) not null comment 'secret value',
seed char(32) not null comment 'seed for new tokens by this consumer',
created datetime not null comment 'date this record was created',
@ -188,6 +189,8 @@ create table token (
secret char(32) not null comment 'secret value',
type tinyint not null default 0 comment 'request or access',
state tinyint default 0 comment 'for requests, 0 = initial, 1 = authorized, 2 = used',
verifier varchar(255) comment 'verifier string for OAuth 1.0a',
verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a',
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified',
@ -207,6 +210,33 @@ create table nonce (
constraint primary key (consumer_key, ts, nonce)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table oauth_application (
id integer auto_increment primary key comment 'unique identifier',
owner integer not null comment 'owner of the application' references profile (id),
consumer_key varchar(255) not null comment 'application consumer key' references consumer (consumer_key),
name varchar(255) not null comment 'name of the application',
description varchar(255) comment 'description of the application',
icon varchar(255) not null comment 'application icon',
source_url varchar(255) comment 'application homepage - used for source link',
organization varchar(255) comment 'name of the organization running the application',
homepage varchar(255) comment 'homepage for the organization',
callback_url varchar(255) comment 'url to redirect to after authentication',
type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop',
access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write',
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified'
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table oauth_application_user (
profile_id integer not null comment 'user of the application' references profile (id),
application_id integer not null comment 'id of the application' references oauth_application (id),
access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked',
token varchar(255) comment 'request or access token',
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified',
constraint primary key (profile_id, application_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
/* These are used by JanRain OpenID library */
create table oid_associations (

View File

@ -53,6 +53,9 @@ if (!defined('STATUSNET')) {
class ApiAction extends Action
{
const READ_ONLY = 1;
const READ_WRITE = 2;
var $format = null;
var $user = null;
var $auth_user = null;
@ -62,6 +65,8 @@ class ApiAction extends Action
var $since_id = null;
var $since = null;
var $access = self::READ_ONLY; // read (default) or read-write
/**
* Initialization.
*

View File

@ -28,7 +28,7 @@
* @author Evan Prodromou <evan@status.net>
* @author mEDI <medi@milaro.net>
* @author Sarven Capadisli <csarven@status.net>
* @author Zach Copley <zach@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
@ -39,6 +39,7 @@ if (!defined('STATUSNET')) {
}
require_once INSTALLDIR . '/lib/api.php';
require_once INSTALLDIR . '/lib/apioauth.php';
/**
* Actions extending this class will require auth
@ -52,6 +53,9 @@ require_once INSTALLDIR . '/lib/api.php';
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
@ -67,12 +71,118 @@ class ApiAuthAction extends ApiAction
parent::prepare($args);
if ($this->requiresAuth()) {
$this->checkBasicAuthUser();
$this->consumer_key = $this->arg('oauth_consumer_key');
$this->access_token = $this->arg('oauth_token');
if (!empty($this->access_token)) {
$this->checkOAuthRequest();
} else {
$this->checkBasicAuthUser();
// By default, all basic auth users have read and write access
$this->access = self::READ_WRITE;
}
}
return true;
}
function handle($args)
{
parent::handle($args);
}
function checkOAuthRequest()
{
common_debug("We have an OAuth request.");
$datastore = new ApiStatusNetOAuthDataStore();
$server = new OAuthServer($datastore);
$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
$server->add_signature_method($hmac_method);
ApiOauthAction::cleanRequest();
try {
$req = OAuthRequest::from_request();
$server->verify_request($req);
$app = Oauth_application::getByConsumerKey($this->consumer_key);
if (empty($app)) {
// this should really not happen
common_log(LOG_WARN,
"Couldn't find the OAuth app for consumer key: $this->consumer_key");
throw new OAuthException('No application for that consumer key.');
}
// set the source attr
$this->oauth_source = $app->name;
$appUser = Oauth_application_user::staticGet('token',
$this->access_token);
// XXX: check that app->id and appUser->application_id and consumer all
// match?
if (!empty($appUser)) {
// read or read-write
$this->oauth_access_type = $appUser->access_type;
// If access_type == 0 we have either a request token
// or a bad / revoked access token
if ($this->oauth_access_type != 0) {
// 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;
if (Event::handle('StartSetApiUser', array(&$user))) {
$this->auth_user = User::staticGet('id', $appUser->profile_id);
Event::handle('EndSetApiUser', array($user));
}
$msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " .
"application '%s' (id: %d).";
common_log(LOG_INFO, sprintf($msg,
$this->auth_user->nickname,
$this->auth_user->id,
$app->name,
$app->id));
return true;
} else {
throw new OAuthException('Bad access token.');
}
} else {
// also should not happen
throw new OAuthException('No user for that token.');
}
} catch (OAuthException $e) {
common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage());
common_debug(var_export($req, true));
$this->showOAuthError($e->getMessage());
exit();
}
}
function showOAuthError($msg)
{
header('HTTP/1.1 401 Unauthorized');
header('Content-Type: text/html; charset=utf-8');
print $msg . "\n";
}
/**
* Does this API resource require authentication?
*
@ -128,6 +238,7 @@ class ApiAuthAction extends ApiAction
exit;
}
}
return true;
}

122
lib/apioauth.php Normal file
View File

@ -0,0 +1,122 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Base action for OAuth API endpoints
*
* 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/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 <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 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);
}
}
}

163
lib/apioauthstore.php Normal file
View File

@ -0,0 +1,163 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008, 2009, StatusNet, Inc.
*
* 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/>.
*/
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 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__);
$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 ($rt->find(true) && $rt->state == 1) { // authorized
common_debug('request token found.', __FILE__);
// 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. app id $app->id token $rt->tok");
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()) {
$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 the token from req to access for the user
$orig = clone($appUser);
$appUser->token = $at->tok;
// 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.
$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
return new OAuthToken($at->tok, $at->secret);
}
} else {
return null;
}
}
}

338
lib/applicationeditform.php Normal file
View File

@ -0,0 +1,338 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Form for editing an 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 Form
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @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 <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 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';
}
}
/**
* 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
*
* @return string of the form class
*/
function formClass()
{
return 'form_settings';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
$cur = common_current_user();
if (!empty($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, _('Edit application'));
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
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;
$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 = '';
$icon = '';
$name = '';
$description = '';
$source_url = '';
$organization = '';
$homepage = '';
$callback_url = '';
$this->type = '';
$this->access_type = '';
}
$this->out->hidden('token', common_session_token());
$this->out->elementStart('ul', 'form_data');
$this->out->elementStart('li', array('id' => 'application_icon'));
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->input('name', _('Name'),
($this->out->arg('name')) ? $this->out->arg('name') : $name);
$this->out->elementEnd('li');
$this->out->elementStart('li');
$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->elementStart('li');
$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 for 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', array('id' => 'application_types'));
$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->application->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', array('id' => 'default_access_types'));
$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');
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
$this->out->submit('cancel', _('Cancel'), 'submit form_action-primary',
'cancel', _('Cancel'));
$this->out->submit('save', _('Save'), 'submit form_action-secondary',
'save', _('Save'));
}
}

168
lib/applicationlist.php Normal file
View File

@ -0,0 +1,168 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Widget to show a list of OAuth 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 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);
}
require_once INSTALLDIR . '/lib/widget.php';
define('APPS_PER_PAGE', 20);
/**
* Widget to show a list of OAuth applications
*
* @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 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, $connections = false)
{
parent::__construct($action);
$this->application = $application;
$this->owner = $owner;
$this->action = $action;
$this->connections = $connections;
}
function show()
{
$this->out->elementStart('ul', 'applications');
$cnt = 0;
while ($this->application->fetch()) {
$cnt++;
if($cnt > APPS_PER_PAGE) {
break;
}
$this->showapplication();
}
$this->out->elementEnd('ul');
return $cnt;
}
function showApplication()
{
$user = common_current_user();
$this->out->elementStart('li', array('class' => 'application',
'id' => 'oauthclient-' . $this->application->id));
$this->out->elementStart('span', 'vcard author');
if (!$this->connections) {
$this->out->elementStart('a',
array('href' => common_local_url('showapplication',
array('id' => $this->application->id)),
'class' => 'url'));
} else {
$this->out->elementStart('a', array('href' => $this->application->source_url,
'class' => 'url'));
}
if (!empty($this->application->icon)) {
$this->out->element('img', array('src' => $this->application->icon,
'class' => 'photo avatar'));
}
$this->out->element('span', 'fn', $this->application->name);
$this->out->elementEnd('a');
$this->out->elementEnd('span');
$this->out->raw(' by ');
$this->out->element('a', array('href' => $this->application->homepage,
'class' => 'url'),
$this->application->organization);
$this->out->element('p', 'note', $this->application->description);
$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_date_string($appUser->modified) .
" - $access access.";
$this->out->raw($txt);
$this->out->elementEnd('li');
$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');
}
}
/* Override this in subclasses. */
function showOwnerControls()
{
return;
}
}

View File

@ -115,6 +115,11 @@ class ConnectSettingsNav extends Widget
array(_('SMS'),
_('Updates by SMS'));
}
$menu['oauthconnectionssettings'] = array(
_('Connections'),
_('Authorized connected applications')
);
foreach ($menu as $menuaction => $menudesc) {
$this->action->menuItem(common_local_url($menuaction),
@ -131,4 +136,3 @@ class ConnectSettingsNav extends Widget
}

View File

@ -211,6 +211,8 @@ $default =
'uploads' => true,
'filecommand' => '/usr/bin/file',
),
'application' =>
array('desclimit' => null),
'group' =>
array('maxaliases' => 3,
'desclimit' => null),

View File

@ -88,7 +88,7 @@ abstract class IoMaster
$sn = new Status_network();
$sn->find();
while ($sn->fetch()) {
$hosts[] = $sn->hostname;
$hosts[] = $sn->getServerName();
}
return $hosts;
}

View File

@ -140,8 +140,8 @@ class Router
// settings
foreach (array('profile', 'avatar', 'password', 'im',
'email', 'sms', 'userdesign', 'other') as $s) {
foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections',
'oauthapps', 'email', 'sms', 'userdesign', 'other') as $s) {
$m->connect('settings/'.$s, array('action' => $s.'settings'));
}
@ -641,6 +641,27 @@ class Router
array('nickname' => '[a-zA-Z0-9]{1,64}'));
}
$m->connect('settings/oauthapps/show/:id',
array('action' => 'showapplication'),
array('id' => '[0-9]+')
);
$m->connect('settings/oauthapps/new',
array('action' => 'newapplication')
);
$m->connect('settings/oauthapps/edit/:id',
array('action' => 'editapplication'),
array('id' => '[0-9]+')
);
$m->connect('api/oauth/request_token',
array('action' => 'apioauthrequesttoken'));
$m->connect('api/oauth/access_token',
array('action' => 'apioauthaccesstoken'));
$m->connect('api/oauth/authorize',
array('action' => 'apioauthauthorize'));
foreach (array('subscriptions', 'subscribers') as $a) {
$m->connect(':nickname/'.$a.'/:tag',
array('action' => $a),

22
tests/oauth/README Normal file
View File

@ -0,0 +1,22 @@
Some very rough test scripts for hitting up the OAuth endpoints.
Note: this works best if you register an OAuth application, leaving
the callback URL blank.
Put your instance info and consumer key and secret in oauth.ini
Example usage:
--------------
php getrequesttoken.php
Gets a request token, token secret and a url to authorize it. Once
you authorize the request token you can exchange it for an access token...
php exchangetokens.php --oauth_token=b9a79548a88c1aa9a5bea73103c6d41d --token_secret=4a47d9337fc0202a14ab552e17a3b657
Once you have your access token, go ahead and try a protected API
resource:
php verifycreds.php --oauth_token=cf2de7665f0dda0a82c2dc39b01be7f9 --token_secret=4524c3b712200138e1a4cff2e9ca83d8

105
tests/oauth/exchangetokens.php Executable file
View File

@ -0,0 +1,105 @@
#!/usr/bin/env php
<?php
/*
* StatusNet - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, StatusNet, Inc.
*
* 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/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
require_once INSTALLDIR . '/extlib/OAuth.php';
$ini = parse_ini_file("oauth.ini");
$test_consumer = new OAuthConsumer($ini['consumer_key'], $ini['consumer_secret']);
$at_endpoint = $ini['apiroot'] . $ini['access_token_url'];
$shortoptions = 't:s:';
$longoptions = array('oauth_token=', 'token_secret=');
$helptext = <<<END_OF_ETOKENS_HELP
exchangetokens.php [options]
Exchange an authorized OAuth request token for an access token
-t --oauth_token authorized request token
-s --token_secret authorized request token secret
END_OF_ETOKENS_HELP;
require_once INSTALLDIR . '/scripts/commandline.inc';
$token = null;
$token_secret = null;
if (have_option('t', 'oauth_token')) {
$token = get_option_value('oauth_token');
}
if (have_option('s', 'token_secret')) {
$token_secret = get_option_value('s', 'token_secret');
}
if (empty($token)) {
print "Please specify a request token.\n";
exit(1);
}
if (empty($token_secret)) {
print "Please specify a request token secret.\n";
exit(1);
}
$rt = new OAuthToken($token, $token_secret);
common_debug("Exchange request token = " . var_export($rt, true));
$parsed = parse_url($at_endpoint);
$params = array();
parse_str($parsed['query'], $params);
$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
$req_req = OAuthRequest::from_consumer_and_token($test_consumer, $rt, "GET", $at_endpoint, $params);
$req_req->sign_request($hmac_method, $test_consumer, $rt);
$r = httpRequest($req_req->to_url());
common_debug("Exchange request token = " . var_export($rt, true));
common_debug("Exchange tokens URL: " . $req_req->to_url());
$body = $r->getBody();
$token_stuff = array();
parse_str($body, $token_stuff);
print 'Access token : ' . $token_stuff['oauth_token'] . "\n";
print 'Access token secret : ' . $token_stuff['oauth_token_secret'] . "\n";
function httpRequest($url)
{
$request = HTTPClient::start();
$request->setConfig(array(
'follow_redirects' => true,
'connect_timeout' => 120,
'timeout' => 120,
'ssl_verify_peer' => false,
'ssl_verify_host' => false
));
return $request->get($url);
}

71
tests/oauth/getrequesttoken.php Executable file
View File

@ -0,0 +1,71 @@
#!/usr/bin/env php
<?php
/*
* StatusNet - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, StatusNet, Inc.
*
* 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/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
require_once INSTALLDIR . '/scripts/commandline.inc';
require_once INSTALLDIR . '/extlib/OAuth.php';
$ini = parse_ini_file("oauth.ini");
$test_consumer = new OAuthConsumer($ini['consumer_key'], $ini['consumer_secret']);
$rt_endpoint = $ini['apiroot'] . $ini['request_token_url'];
$parsed = parse_url($rt_endpoint);
$params = array();
parse_str($parsed['query'], $params);
$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
$req_req = OAuthRequest::from_consumer_and_token($test_consumer, NULL, "GET", $rt_endpoint, $params);
$req_req->sign_request($hmac_method, $test_consumer, NULL);
$r = httpRequest($req_req->to_url());
$body = $r->getBody();
$token_stuff = array();
parse_str($body, $token_stuff);
$authurl = $ini['apiroot'] . $ini['authorize_url'] . '?oauth_token=' . $token_stuff['oauth_token'];
print 'Request token : ' . $token_stuff['oauth_token'] . "\n";
print 'Request token secret : ' . $token_stuff['oauth_token_secret'] . "\n";
print "Authorize URL : $authurl\n";
//var_dump($req_req);
function httpRequest($url)
{
$request = HTTPClient::start();
$request->setConfig(array(
'follow_redirects' => true,
'connect_timeout' => 120,
'timeout' => 120,
'ssl_verify_peer' => false,
'ssl_verify_host' => false
));
return $request->get($url);
}

10
tests/oauth/oauth.ini Normal file
View File

@ -0,0 +1,10 @@
; Setup OAuth info here
apiroot = "http://YOURSTATUSNET/api"
request_token_url = "/oauth/request_token"
authorize_url = "/oauth/authorize"
access_token_url = "/oauth/access_token"
consumer_key = "b748968e9bea81a53f3a3c15aa0c686f"
consumer_secret = "5434e18cce05d9e53cdd48029a62fa41"

101
tests/oauth/verifycreds.php Executable file
View File

@ -0,0 +1,101 @@
#!/usr/bin/env php
<?php
/*
* StatusNet - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, StatusNet, Inc.
*
* 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/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
require_once INSTALLDIR . '/extlib/OAuth.php';
$shortoptions = 'o:s:';
$longoptions = array('oauth_token=', 'token_secret=');
$helptext = <<<END_OF_VERIFY_HELP
verifycreds.php [options]
Use an access token to verify credentials thru the api
-o --oauth_token access token
-s --token_secret access token secret
END_OF_VERIFY_HELP;
$token = null;
$token_secret = null;
require_once INSTALLDIR . '/scripts/commandline.inc';
if (have_option('o', 'oauth_token')) {
$token = get_option_value('oauth_token');
}
if (have_option('s', 'token_secret')) {
$token_secret = get_option_value('s', 'token_secret');
}
if (empty($token)) {
print "Please specify an access token.\n";
exit(1);
}
if (empty($token_secret)) {
print "Please specify an access token secret.\n";
exit(1);
}
$ini = parse_ini_file("oauth.ini");
$test_consumer = new OAuthConsumer($ini['consumer_key'], $ini['consumer_secret']);
$endpoint = $ini['apiroot'] . '/account/verify_credentials.xml';
print "$endpoint\n";
$at = new OAuthToken($token, $token_secret);
$parsed = parse_url($endpoint);
$params = array();
parse_str($parsed['query'], $params);
$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
$req_req = OAuthRequest::from_consumer_and_token($test_consumer, $at, "GET", $endpoint, $params);
$req_req->sign_request($hmac_method, $test_consumer, $at);
$r = httpRequest($req_req->to_url());
$body = $r->getBody();
print "$body\n";
//print $req_req->to_url() . "\n\n";
function httpRequest($url)
{
$request = HTTPClient::start();
$request->setConfig(array(
'follow_redirects' => true,
'connect_timeout' => 120,
'timeout' => 120,
'ssl_verify_peer' => false,
'ssl_verify_host' => false
));
return $request->get($url);
}

View File

@ -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 {
.form_data #application_icon img,
#apioauthauthorize .form_data img {
max-width:96px;
max-height:96px;
}
#apioauthauthorize .form_data img {
margin-right:18px;
float:left;
}
#showapplication .entity_profile {
width:68%;
}
@ -938,9 +944,9 @@ margin-left:1.795%;
font-family:monospace;
font-size:1.3em;
}
#editapplication .form_data #application_types label.radio,
#editapplication .form_data #default_access_types label.radio {
width:15%;
.form_data #application_types label.radio,
.form_data #default_access_types label.radio {
width:14.5%;
}
/* NOTICE */