gnu-social/lib/oauthstore.php

504 lines
17 KiB
PHP
Raw Normal View History

<?php
/*
2009-08-26 07:14:12 +09:00
* 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); }
2009-08-10 21:48:50 +09:00
require_once 'libomb/datastore.php';
class StatusNetOAuthDataStore extends OAuthDataStore
{
// We keep a record of who's contacted us
function lookup_consumer($consumer_key)
{
$con = Consumer::staticGet('consumer_key', $consumer_key);
if (!$con) {
$con = new Consumer();
$con->consumer_key = $consumer_key;
$con->seed = common_good_rand(16);
$con->created = DB_DataObject_Cast::dateTime();
if (!$con->insert()) {
return null;
}
}
return new OAuthConsumer($con->consumer_key, '');
}
function lookup_token($consumer, $token_type, $token_key)
{
$t = new Token();
2009-08-10 21:48:50 +09:00
if (!is_null($consumer)) {
$t->consumer_key = $consumer->key;
}
$t->tok = $token_key;
$t->type = ($token_type == 'access') ? 1 : 0;
if ($t->find(true)) {
return new OAuthToken($t->tok, $t->secret);
} else {
return null;
}
}
// http://oauth.net/core/1.0/#nonce
// "The Consumer SHALL then generate a Nonce value that is unique for
// all requests with that timestamp."
// XXX: It's not clear why the token is here
function lookup_nonce($consumer, $token, $nonce, $timestamp)
{
$n = new Nonce();
$n->consumer_key = $consumer->key;
$n->ts = $timestamp;
$n->nonce = $nonce;
if ($n->find(true)) {
return true;
} else {
$n->created = DB_DataObject_Cast::dateTime();
$n->insert();
return false;
}
}
function new_request_token($consumer)
{
$t = new Token();
$t->consumer_key = $consumer->key;
$t->tok = common_good_rand(16);
$t->secret = common_good_rand(16);
$t->type = 0; // request
$t->state = 0; // unauthorized
$t->created = DB_DataObject_Cast::dateTime();
if (!$t->insert()) {
return null;
} else {
return new OAuthToken($t->tok, $t->secret);
}
}
// defined in OAuthDataStore, but not implemented anywhere
function fetch_request_token($consumer)
{
return $this->new_request_token($consumer);
}
function new_access_token($token, $consumer)
{
common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__);
$rt = new Token();
$rt->consumer_key = $consumer->key;
$rt->tok = $token->key;
$rt->type = 0; // request
if ($rt->find(true) && $rt->state == 1) { // authorized
common_debug('request token found.', __FILE__);
$at = new Token();
$at->consumer_key = $consumer->key;
$at->tok = common_good_rand(16);
$at->secret = common_good_rand(16);
$at->type = 1; // access
$at->created = DB_DataObject_Cast::dateTime();
if (!$at->insert()) {
$e = $at->_lastError;
common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__);
return null;
} else {
common_debug('access token "'.$at->tok.'" inserted', __FILE__);
// burn the old one
$orig_rt = clone($rt);
$rt->state = 2; // used
if (!$rt->update($orig_rt)) {
return null;
}
common_debug('request token "'.$rt->tok.'" updated', __FILE__);
// Update subscription
// XXX: mixing levels here
$sub = Subscription::staticGet('token', $rt->tok);
if (!$sub) {
return null;
}
common_debug('subscription for request token found', __FILE__);
$orig_sub = clone($sub);
$sub->token = $at->tok;
$sub->secret = $at->secret;
if (!$sub->update($orig_sub)) {
return null;
} else {
common_debug('subscription updated to use access token', __FILE__);
return new OAuthToken($at->tok, $at->secret);
}
}
} else {
return null;
}
}
// defined in OAuthDataStore, but not implemented anywhere
function fetch_access_token($consumer)
{
return $this->new_access_token($consumer);
}
2009-08-10 21:48:50 +09:00
/**
* Revoke specified OAuth token
*
* Revokes the authorization token specified by $token_key.
* Throws exceptions in case of error.
*
* @param string $token_key The token to be revoked
*
* @access public
**/
public function revoke_token($token_key) {
$rt = new Token();
$rt->tok = $token_key;
$rt->type = 0;
$rt->state = 0;
if (!$rt->find(true)) {
throw new Exception('Tried to revoke unknown token');
}
if (!$rt->delete()) {
throw new Exception('Failed to delete revoked token');
}
}
/**
* Authorize specified OAuth token
*
* Authorizes the authorization token specified by $token_key.
* Throws exceptions in case of error.
*
* @param string $token_key The token to be authorized
*
* @access public
**/
public function authorize_token($token_key) {
$rt = new Token();
$rt->tok = $token_key;
$rt->type = 0;
$rt->state = 0;
if (!$rt->find(true)) {
throw new Exception('Tried to authorize unknown token');
}
$orig_rt = clone($rt);
$rt->state = 1; # Authorized but not used
if (!$rt->update($orig_rt)) {
throw new Exception('Failed to authorize token');
}
}
/**
* Get profile by identifying URI
*
* Returns an OMB_Profile object representing the OMB profile identified by
* $identifier_uri.
* Returns null if there is no such OMB profile.
* Throws exceptions in case of other error.
*
* @param string $identifier_uri The OMB identifier URI specifying the
* requested profile
*
* @access public
*
* @return OMB_Profile The corresponding profile
**/
public function getProfile($identifier_uri) {
/* getProfile is only used for remote profiles by libomb.
TODO: Make it work with local ones anyway. */
$remote = Remote_profile::staticGet('uri', $identifier_uri);
if (!$remote) throw new Exception('No such remote profile');
$profile = Profile::staticGet('id', $remote->id);
if (!$profile) throw new Exception('No profile for remote user');
require_once INSTALLDIR.'/lib/omb.php';
return profile_to_omb_profile($identifier_uri, $profile);
}
/**
* Save passed profile
*
* Stores the OMB profile $profile. Overwrites an existing entry.
* Throws exceptions in case of error.
*
* @param OMB_Profile $profile The OMB profile which should be saved
*
* @access public
**/
public function saveProfile($omb_profile) {
if (common_profile_url($omb_profile->getNickname()) ==
$omb_profile->getProfileURL()) {
throw new Exception('Not implemented');
} else {
$remote = Remote_profile::staticGet('uri', $omb_profile->getIdentifierURI());
if ($remote) {
$exists = true;
$profile = Profile::staticGet($remote->id);
$orig_remote = clone($remote);
$orig_profile = clone($profile);
# XXX: compare current postNotice and updateProfile URLs to the ones
# stored in the DB to avoid (possibly...) above attack
} else {
$exists = false;
$remote = new Remote_profile();
$remote->uri = $omb_profile->getIdentifierURI();
$profile = new Profile();
}
$profile->nickname = $omb_profile->getNickname();
$profile->profileurl = $omb_profile->getProfileURL();
$fullname = $omb_profile->getFullname();
$profile->fullname = is_null($fullname) ? '' : $fullname;
$homepage = $omb_profile->getHomepage();
$profile->homepage = is_null($homepage) ? '' : $homepage;
$bio = $omb_profile->getBio();
$profile->bio = is_null($bio) ? '' : $bio;
$location = $omb_profile->getLocation();
$profile->location = is_null($location) ? '' : $location;
if ($exists) {
$profile->update($orig_profile);
} else {
$profile->created = DB_DataObject_Cast::dateTime(); # current time
$id = $profile->insert();
if (!$id) {
throw new Exception(_('Error inserting new profile'));
}
$remote->id = $id;
}
$avatar_url = $omb_profile->getAvatarURL();
if ($avatar_url) {
if (!$this->add_avatar($profile, $avatar_url)) {
throw new Exception(_('Error inserting avatar'));
}
} else {
$avatar = $profile->getOriginalAvatar();
if($avatar) $avatar->delete();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
if($avatar) $avatar->delete();
$avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
if($avatar) $avatar->delete();
$avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
if($avatar) $avatar->delete();
}
if ($exists) {
if (!$remote->update($orig_remote)) {
throw new Exception(_('Error updating remote profile'));
}
} else {
$remote->created = DB_DataObject_Cast::dateTime(); # current time
if (!$remote->insert()) {
throw new Exception(_('Error inserting remote profile'));
}
}
}
}
function add_avatar($profile, $url)
{
$temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
copy($url, $temp_filename);
$imagefile = new ImageFile($profile->id, $temp_filename);
$filename = Avatar::filename($profile->id,
image_type_to_extension($imagefile->type),
null,
common_timestamp());
rename($temp_filename, Avatar::path($filename));
return $profile->setOriginal($filename);
}
/**
* Save passed notice
*
* Stores the OMB notice $notice. The datastore may change the passed notice.
* This might by neccessary for URIs depending on a database key. Note that
* it is the users duty to present a mechanism for his OMB_Datastore to
* appropriately change his OMB_Notice.
* Throws exceptions in case of error.
*
* @param OMB_Notice $notice The OMB notice which should be saved
*
* @access public
**/
public function saveNotice(&$omb_notice) {
if (Notice::staticGet('uri', $omb_notice->getIdentifierURI())) {
throw new Exception(_('Duplicate notice'));
}
$author_uri = $omb_notice->getAuthor()->getIdentifierURI();
common_log(LOG_DEBUG, $author_uri, __FILE__);
$author = Remote_profile::staticGet('uri', $author_uri);
if (!$author) {
$author = User::staticGet('uri', $author_uri);
}
if (!$author) {
2009-11-09 07:10:44 +09:00
throw new Exception('No such user.');
2009-08-10 21:48:50 +09:00
}
common_log(LOG_DEBUG, print_r($author, true), __FILE__);
$notice = Notice::saveNew($author->id,
$omb_notice->getContent(),
'omb',
array('is_local' => Notice::REMOTE_OMB,
'uri' => $omb_notice->getIdentifierURI()));
Offload inbox updates to a queue handler to speed up posting online Moved much of the writing that happens when posting a notice to a new queuehandler, distribqueuehandler. This updates tags, groups, replies and inboxes at queue time (or at Web time, if queues are disabled). To make this work well, I had to break up the monolithic Notice::blowCaches() and make cache blowing happen closer to where data is updated. Squashed commit of the following: commit 5257626c62750ac4ac1db0ce2b71410c5711cfa3 Author: Evan Prodromou <evan@status.net> Date: Mon Jan 25 14:56:41 2010 -0500 slightly better handling of blowing tag memory cache commit 8a22a3cdf6ec28685da129a0313e7b2a0837c9ef Author: Evan Prodromou <evan@status.net> Date: Mon Jan 25 01:42:56 2010 -0500 change 'distribute' to 'distrib' so not too long for dbqueue commit 7a063315b0f7fad27cb6fbd2bdd74e253af83e4f Author: Evan Prodromou <evan@status.net> Date: Mon Jan 25 01:39:15 2010 -0500 change handle_notice() to handle() in distributqueuehandler commit 1a39ccd28b9994137d7bfd21bb4f230546938e77 Author: Evan Prodromou <evan@status.net> Date: Mon Jan 25 16:05:25 2010 -0500 error with queuemanager commit e6b3bb93f305cfd2de71a6340b8aa6fb890049b7 Author: Evan Prodromou <evan@status.net> Date: Mon Jan 25 01:11:34 2010 -0500 Blow memcache at different point rather than one big function for Notice class commit 94d557cdc016187d1d0647ae1794cd94d6fb8ac8 Author: Evan Prodromou <evan@status.net> Date: Mon Jan 25 00:48:44 2010 -0500 Blow memcache at different point rather than one big function for Notice class commit 1c781dd08c88a35dafc5c01230b4872fd6b95182 Author: Evan Prodromou <evan@status.net> Date: Wed Jan 20 08:54:18 2010 -0500 move broadcasting and distributing to new queuehandler commit da3e46d26b84e4f028f34a13fd2ee373e4c1b954 Author: Evan Prodromou <evan@status.net> Date: Wed Jan 20 08:53:12 2010 -0500 Move distribution of notices to new distribute queue handler
2010-01-26 08:08:21 +09:00
2009-08-10 21:48:50 +09:00
}
/**
* Get subscriptions of a given profile
*
* Returns an array containing subscription informations for the specified
* profile. Every array entry should in turn be an array with keys
* 'uri´: The identifier URI of the subscriber
* 'token´: The subscribe token
* 'secret´: The secret token
* Throws exceptions in case of error.
*
* @param string $subscribed_user_uri The OMB identifier URI specifying the
* subscribed profile
*
* @access public
*
* @return mixed An array containing the subscriptions or 0 if no
* subscription has been found.
**/
public function getSubscriptions($subscribed_user_uri) {
$sub = new Subscription();
$user = $this->_getAnyProfile($subscribed_user_uri);
$sub->subscribed = $user->id;
if (!$sub->find(true)) {
return 0;
}
/* Since we do not use OMB_Service_Providers action methods, there
is no need to actually return the subscriptions. */
return 1;
}
private function _getAnyProfile($uri)
{
$user = Remote_profile::staticGet('uri', $uri);
if (!$user) {
$user = User::staticGet('uri', $uri);
}
if (!$user) {
2009-11-09 07:10:44 +09:00
throw new Exception('No such user.');
2009-08-10 21:48:50 +09:00
}
return $user;
}
/**
* Delete a subscription
*
* Deletes the subscription from $subscriber_uri to $subscribed_user_uri.
* Throws exceptions in case of error.
*
* @param string $subscriber_uri The OMB identifier URI specifying the
* subscribing profile
*
* @param string $subscribed_user_uri The OMB identifier URI specifying the
* subscribed profile
*
* @access public
**/
public function deleteSubscription($subscriber_uri, $subscribed_user_uri)
{
$sub = new Subscription();
$subscribed = $this->_getAnyProfile($subscribed_user_uri);
$subscriber = $this->_getAnyProfile($subscriber_uri);
$sub->subscribed = $subscribed->id;
$sub->subscriber = $subscriber->id;
$sub->delete();
}
/**
* Save a subscription
*
* Saves the subscription from $subscriber_uri to $subscribed_user_uri.
* Throws exceptions in case of error.
*
* @param string $subscriber_uri The OMB identifier URI specifying
* the subscribing profile
*
* @param string $subscribed_user_uri The OMB identifier URI specifying
* the subscribed profile
* @param OAuthToken $token The access token
*
* @access public
**/
public function saveSubscription($subscriber_uri, $subscribed_user_uri,
$token)
{
$sub = new Subscription();
$subscribed = $this->_getAnyProfile($subscribed_user_uri);
$subscriber = $this->_getAnyProfile($subscriber_uri);
2009-11-17 04:12:35 +09:00
if (!$subscriber->hasRight(Right::SUBSCRIBE)) {
common_log(LOG_INFO, __METHOD__ . ": remote subscriber banned ($subscriber_uri subbing to $subscribed_user_uri)");
2009-11-17 04:12:35 +09:00
return _('You have been banned from subscribing.');
}
2009-08-10 21:48:50 +09:00
$sub->subscribed = $subscribed->id;
$sub->subscriber = $subscriber->id;
$sub_exists = $sub->find(true);
if ($sub_exists) {
$orig_sub = clone($sub);
} else {
$sub->created = DB_DataObject_Cast::dateTime();
}
$sub->token = $token->key;
$sub->secret = $token->secret;
if ($sub_exists) {
$result = $sub->update($orig_sub);
} else {
$result = $sub->insert();
}
if (!$result) {
common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__);
throw new Exception(_('Couldn\'t insert new subscription.'));
return;
}
/* Notify user, if necessary. */
if ($subscribed instanceof User) {
mail_subscribe_notify_profile($subscribed,
Profile::staticGet($subscriber->id));
}
}
}
2009-08-10 21:48:50 +09:00
?>