finishremotesubscribe.php 6.73 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
<?php
/*
 * Laconica - a distributed open-source microblogging tool
 * Copyright (C) 2008, Controlez-Vous, 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('LACONICA')) { exit(1); }

require_once(INSTALLDIR.'/lib/omb.php');

class FinishremotesubscribeAction extends Action {
	
	function handle($args) {
		
		parent::handle($args);

		if (common_logged_in()) {
			common_user_error(_t('You can use the local subscription!'));
		    return;
		}
		
35
		$omb = $_SESSION['oauth_authorization_request'];
36 37 38 39 40 41
		
		if (!$omb) {
			common_user_error(_t('Not expecting this response!'));
			return;
		}

42 43
		common_debug('stored request: '.print_r($omb,true), __FILE__);
		
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
		$req = OAuthRequest::from_request();

		$token = $req->get_parameter('oauth_token');

		# I think this is the success metric
		
		if ($token != $omb['token']) {
			common_user_error(_t('Not authorized.'));
			return;
		}
		
		$version = $req->get_parameter('omb_version');
		
		if ($version != OMB_VERSION_01) {
			common_user_error(_t('Unknown version of OMB protocol.'));
			return;
		}
		
		$nickname = $req->get_parameter('omb_listener_nickname');
		
		if (!$nickname) {
			common_user_error(_t('No nickname provided by remote server.'));
			return;
		}

		$profile_url = $req->get_parameter('omb_listener_profile');
		
		if (!$profile_url) {
			common_user_error(_t('No profile URL returned by server.'));
			return;
		}

		if (!Validate::uri($profile_url, array('allowed_schemes' => array('http', 'https')))) {
			common_user_error(_t('Invalid profile URL returned by server.'));
			return;
		}

81 82
		common_debug('listenee: "'.$omb['listenee'].'"', __FILE__);
		
83
		$user = User::staticGet('nickname', $omb['listenee']);
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
		
		if (!$user) {
			common_user_error(_t('User being listened to doesn\'t exist.'));
			return;
		}
		
		$fullname = $req->get_parameter('omb_listener_fullname');
		$homepage = $req->get_parameter('omb_listener_homepage');
		$bio = $req->get_parameter('omb_listener_bio');
		$location = $req->get_parameter('omb_listener_location');
		$avatar_url = $req->get_parameter('omb_listener_avatar');

		list($newtok, $newsecret) = $this->access_token($omb);
		
		if (!$newtok || !$newsecret) {
			common_user_error(_t('Couldn\'t convert request tokens to access tokens.'));
			return;
		}
		
		# XXX: possible attack point; subscribe and return someone else's profile URI
		
		$remote = Remote_profile::staticGet('uri', $omb['listener']);
		
		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['listener'];
			$profile = new Profile();
		}

		$profile->nickname = $nickname;
		$profile->profileurl = $profile_url;
		
		if ($fullname) {
			$profile->fullname = $fullname;
		}
		if ($homepage) {
			$profile->homepage = $homepage;
		}
		if ($bio) {
			$profile->bio = $bio;
		}
		if ($location) {
			$profile->location = $location;
		}
		
		if ($exists) {
			$profile->update($orig_profile);
		} else {
			$profile->created = DB_DataObject_Cast::dateTime(); # current time
			$id = $profile->insert();
142 143 144 145
			if (!$id) {
				common_server_error(_t('Error inserting new profile'));
				return;
			}
146 147 148 149
			$remote->id = $id;
		}

		if ($avatar_url) {
150 151 152 153
			if (!$this->add_avatar($profile, $avatar_url)) {
				common_server_error(_t('Error inserting avatar'));
				return;
			}
154 155
		}

156 157
		$remote->postnoticeurl = $omb['post_notice_url'];
		$remote->updateprofileurl = $omb['update_profile_url'];
158 159

		if ($exists) {
160 161 162 163
			if (!$remote->update($orig_remote)) {
				common_server_error(_t('Error updating remote profile'));
				return;
			}
164 165
		} else {
			$remote->created = DB_DataObject_Cast::dateTime(); # current time
166 167 168 169
			if (!$remote->insert()) {
				common_server_error(_t('Error inserting remote profile'));
				return;
			}
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
		}
		
		$sub = new Subscription();
		$sub->subscriber = $remote->id;
		$sub->subscribed = $user->id;
		$sub->token = $newtok;
		$sub->secret = $newsecret;
		$sub->created = DB_DataObject_Cast::dateTime(); # current time
		
		if (!$sub->insert()) {
			common_user_error(_t('Couldn\'t insert new subscription.'));
			return;
		}

		# Clear the data
185
		unset($_SESSION['oauth_authorization_request']);
186 187 188 189
		
		# If we show subscriptions in reverse chron order, this should
		# show up close to the top of the page
		
190
		common_redirect(common_local_url('subscribers', array('nickname' =>
191
															 $user->nickname)));
192 193 194 195 196 197
	}
	
	function add_avatar($profile, $url) {
		$temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
		copy($url, $temp_filename);
		return $profile->setOriginal($temp_filename);
198 199 200
	}
	
	function access_token($omb) {
201 202

		common_debug('starting request for access token', __FILE__);
203 204 205 206
		
		$con = omb_oauth_consumer();
		$tok = new OAuthToken($omb['token'], $omb['secret']);

207 208
		common_debug('using request token "'.$tok.'"', __FILE__);
		
209
		$url = $omb['access_token_url'];
210 211

		common_debug('using access token url "'.$url.'"', __FILE__);
212 213 214 215 216 217 218 219 220 221 222 223 224 225
		
		# XXX: Is this the right thing to do? Strip off GET params and make them
		# POST params? Seems wrong to me.
		
		$parsed = parse_url($url);
		$params = array();
		parse_str($parsed['query'], $params);

		$req = OAuthRequest::from_consumer_and_token($con, $tok, "POST", $url, $params);
		
		$req->set_parameter('omb_version', OMB_VERSION_01);
		
		# XXX: test to see if endpoint accepts this signature method

226
		$req->sign_request(omb_hmac_sha1(), $con, $tok);
227 228
		
		# We re-use this tool's fetcher, since it's pretty good
229 230 231

		common_debug('posting to access token url "'.$req->get_normalized_http_url().'"', __FILE__);
		common_debug('posting request data "'.$req->to_postdata().'"', __FILE__);
232 233 234 235
		
		$fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
		$result = $fetcher->post($req->get_normalized_http_url(),
								 $req->to_postdata());
236 237

		common_debug('got result: "'.print_r($result,TRUE).'"', __FILE__);
238 239 240 241 242 243 244 245 246 247
		
		if ($result->status != 200) {
			return NULL;
		}

		parse_str($result->body, $return);
		
		return array($return['oauth_token'], $return['oauth_token_secret']);
	}
}