3 * Let the user authorize a remote subscription request
9 * @author Evan Prodromou <evan@status.net>
10 * @author Robin Millette <millette@status.net>
11 * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
12 * @link http://status.net/
14 * StatusNet - the distributed open-source microblogging tool
15 * Copyright (C) 2008, 2009, StatusNet, Inc.
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU Affero General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU Affero General Public License for more details.
27 * You should have received a copy of the GNU Affero General Public License
28 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
33 require_once INSTALLDIR.'/lib/omb.php';
34 require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
35 require_once INSTALLDIR.'/extlib/libomb/profile.php';
36 define('TIMESTAMP_THRESHOLD', 300);
38 // @todo FIXME: Missing documentation.
39 class UserauthorizationAction extends Action
44 function handle($args)
46 parent::handle($args);
48 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
49 /* Use a session token for CSRF protection. */
50 $token = $this->trimmed('token');
51 if (!$token || $token != common_session_token()) {
52 $srv = $this->getStoredParams();
53 $this->showForm($srv->getRemoteUser(), _('There was a problem ' .
54 'with your session token. Try again, ' .
58 /* We've shown the form, now post user's choice. */
59 $this->sendAuthorization();
61 if (!common_logged_in()) {
62 /* Go log in, and then come back. */
63 common_set_returnto($_SERVER['REQUEST_URI']);
65 common_redirect(common_local_url('login'));
69 $user = common_current_user();
70 $profile = $user->getProfile();
72 common_log_db_error($user, 'SELECT', __FILE__);
73 // TRANS: Server error displayed when trying to authorise a remote subscription request
74 // TRANS: while the user has no profile.
75 $this->serverError(_('User without matching profile.'));
79 /* TODO: If no token is passed the user should get a prompt to enter
80 it according to OAuth Core 1.0. */
83 $srv = new OMB_Service_Provider(
84 profile_to_omb_profile($user->uri, $profile),
85 omb_oauth_datastore());
87 $remote_user = $srv->handleUserAuth();
88 } catch (Exception $e) {
90 $this->clientError($e->getMessage());
94 $this->storeParams($srv);
95 $this->showForm($remote_user);
99 function showForm($params, $error=null)
101 $this->params = $params;
102 $this->error = $error;
108 // TRANS: Page title.
109 return _('Authorize subscription');
112 function showPageNotice()
114 // TRANS: Page notice on "Auhtorize subscription" page.
115 $this->element('p', null, _('Please check these details to make sure '.
116 'that you want to subscribe to this ' .
117 'user’s notices. If you didn’t just ask ' .
118 'to subscribe to someone’s notices, '.
122 function showContent()
124 $params = $this->params;
126 $nickname = $params->getNickname();
127 $profile = $params->getProfileURL();
128 $license = $params->getLicenseURL();
129 $fullname = $params->getFullname();
130 $homepage = $params->getHomepage();
131 $bio = $params->getBio();
132 $location = $params->getLocation();
133 $avatar = $params->getAvatarURL();
135 $this->elementStart('div', 'entity_profile vcard');
136 $this->elementStart('dl', 'entity_depiction');
137 // TRANS: DT element on Authorise Subscription page.
138 $this->element('dt', null, _('Photo'));
139 $this->elementStart('dd');
141 $this->element('img', array('src' => $avatar,
142 'class' => 'photo avatar',
143 'width' => AVATAR_PROFILE_SIZE,
144 'height' => AVATAR_PROFILE_SIZE,
145 'alt' => $nickname));
147 $this->elementEnd('dd');
148 $this->elementEnd('dl');
150 $this->elementStart('dl', 'entity_nickname');
151 // TRANS: DT element on Authorise Subscription page.
152 $this->element('dt', null, _('Nickname'));
153 $this->elementStart('dd');
154 $hasFN = ($fullname !== '') ? 'nickname' : 'fn nickname';
155 $this->elementStart('a', array('href' => $profile,
156 'class' => 'url '.$hasFN));
157 $this->raw($nickname);
158 $this->elementEnd('a');
159 $this->elementEnd('dd');
160 $this->elementEnd('dl');
162 if (!is_null($fullname)) {
163 $this->elementStart('dl', 'entity_fn');
164 $this->elementStart('dd');
165 $this->elementStart('span', 'fn');
166 $this->raw($fullname);
167 $this->elementEnd('span');
168 $this->elementEnd('dd');
169 $this->elementEnd('dl');
171 if (!is_null($location)) {
172 $this->elementStart('dl', 'entity_location');
173 // TRANS: DT element on Authorise Subscription page.
174 $this->element('dt', null, _('Location'));
175 $this->elementStart('dd', 'label');
176 $this->raw($location);
177 $this->elementEnd('dd');
178 $this->elementEnd('dl');
181 if (!is_null($homepage)) {
182 $this->elementStart('dl', 'entity_url');
183 // TRANS: DT element on Authorise Subscription page.
184 $this->element('dt', null, _('URL'));
185 $this->elementStart('dd');
186 $this->elementStart('a', array('href' => $homepage,
188 $this->raw($homepage);
189 $this->elementEnd('a');
190 $this->elementEnd('dd');
191 $this->elementEnd('dl');
194 if (!is_null($bio)) {
195 $this->elementStart('dl', 'entity_note');
196 // TRANS: DT element on Authorise Subscription page where bio is displayed.
197 $this->element('dt', null, _('Note'));
198 $this->elementStart('dd', 'note');
200 $this->elementEnd('dd');
201 $this->elementEnd('dl');
204 if (!is_null($license)) {
205 $this->elementStart('dl', 'entity_license');
206 // TRANS: DT element on Authorise Subscription page where license is displayed.
207 $this->element('dt', null, _('License'));
208 $this->elementStart('dd', 'license');
209 $this->element('a', array('href' => $license,
210 'class' => 'license'),
212 $this->elementEnd('dd');
213 $this->elementEnd('dl');
215 $this->elementEnd('div');
217 $this->elementStart('div', 'entity_actions');
218 $this->elementStart('ul');
219 $this->elementStart('li', 'entity_subscribe');
220 $this->elementStart('form', array('method' => 'post',
221 'id' => 'userauthorization',
222 'class' => 'form_user_authorization',
223 'name' => 'userauthorization',
224 'action' => common_local_url(
225 'userauthorization')));
226 $this->hidden('token', common_session_token());
228 // TRANS: Button text on Authorise Subscription page.
229 $this->submit('accept', _m('BUTTON','Accept'), 'submit accept', null,
230 // TRANS: Title for button on Authorise Subscription page.
231 _('Subscribe to this user.'));
232 // TRANS: Button text on Authorise Subscription page.
233 $this->submit('reject', _m('BUTTON','Reject'), 'submit reject', null,
234 // TRANS: Title for button on Authorise Subscription page.
235 _('Reject this subscription.'));
236 $this->elementEnd('form');
237 $this->elementEnd('li');
238 $this->elementEnd('ul');
239 $this->elementEnd('div');
242 function sendAuthorization()
244 $srv = $this->getStoredParams();
247 // TRANS: Client error displayed for an empty authorisation request.
248 $this->clientError(_('No authorization request!'));
252 $accepted = $this->arg('accept');
254 list($val, $token) = $srv->continueUserAuth($accepted);
255 } catch (Exception $e) {
256 $this->clientError($e->getMessage());
259 if ($val !== false) {
260 common_redirect($val, 303);
261 } elseif ($accepted) {
262 $this->showAcceptMessage($token);
264 $this->showRejectMessage();
268 function showAcceptMessage($tok)
270 // TRANS: Accept message header from Authorise subscription page.
271 common_show_header(_('Subscription authorized'));
272 // TRANS: Accept message text from Authorise subscription page.
273 $this->element('p', null,
274 _('The subscription has been authorized, but no '.
275 'callback URL was passed. Check with the site’s ' .
276 'instructions for details on how to authorize the ' .
277 'subscription. Your subscription token is:'));
278 $this->element('blockquote', 'token', $tok);
279 common_show_footer();
282 function showRejectMessage()
284 // TRANS: Reject message header from Authorise subscription page.
285 common_show_header(_('Subscription rejected'));
286 // TRANS: Reject message from Authorise subscription page.
287 $this->element('p', null,
288 _('The subscription has been rejected, but no '.
289 'callback URL was passed. Check with the site’s ' .
290 'instructions for details on how to fully reject ' .
291 'the subscription.'));
292 common_show_footer();
295 function storeParams($params)
297 common_ensure_session();
298 $_SESSION['userauthorizationparams'] = serialize($params);
301 function clearParams()
303 common_ensure_session();
304 unset($_SESSION['userauthorizationparams']);
307 function getStoredParams()
309 common_ensure_session();
310 $params = unserialize($_SESSION['userauthorizationparams']);
314 function validateOmb()
316 $listener = $_GET['omb_listener'];
317 $listenee = $_GET['omb_listenee'];
318 $nickname = $_GET['omb_listenee_nickname'];
319 $profile = $_GET['omb_listenee_profile'];
321 $user = User::staticGet('uri', $listener);
323 // TRANS: Exception thrown when no valid user is found for an authorisation request.
324 // TRANS: %s is a listener URI.
325 throw new Exception(sprintf(_('Listener URI "%s" not found here.'),
329 if (strlen($listenee) > 255) {
330 // TRANS: Exception thrown when listenee URI is too long for an authorisation request.
331 // TRANS: %s is a listenee URI.
332 throw new Exception(sprintf(_('Listenee URI "%s" is too long.'),
336 $other = User::staticGet('uri', $listenee);
338 // TRANS: Exception thrown when listenee URI is a local user for an authorisation request.
339 // TRANS: %s is a listenee URI.
340 throw new Exception(sprintf(_('Listenee URI "%s" is a local user.'),
344 $remote = Remote_profile::staticGet('uri', $listenee);
346 $sub = new Subscription();
347 $sub->subscriber = $user->id;
348 $sub->subscribed = $remote->id;
349 if ($sub->find(true)) {
350 // TRANS: Exception thrown when already subscribed.
351 throw new Exception('You are already subscribed to this user.');
355 if ($profile == common_profile_url($nickname)) {
356 // TRANS: Exception thrown when profile URL is a local user for an authorisation request.
357 // TRANS: %s is a profile URL.
358 throw new Exception(sprintf(_('Profile URL "%s" is for a local user.'),
363 $license = $_GET['omb_listenee_license'];
364 $site_license = common_config('license', 'url');
365 if (!common_compatible_license($license, $site_license)) {
366 // TRANS: Exception thrown when licenses are not compatible for an authorisation request.
367 // TRANS: %1$s is the license for the listenee, %2$s is the license for "this" StatusNet site.
368 throw new Exception(sprintf(_('Listenee stream license "%1$s" is not ' .
369 'compatible with site license "%2$s".'),
370 $license, $site_license));
373 $avatar = $_GET['omb_listenee_avatar'];
375 if (!common_valid_http_url($avatar) || strlen($avatar) > 255) {
376 // TRANS: Exception thrown when avatar URL is invalid for an authorisation request.
377 // TRANS: %s is an avatar URL.
378 throw new Exception(sprintf(_('Avatar URL "%s" is not valid.'),
381 $size = @getimagesize($avatar);
383 // TRANS: Exception thrown when avatar URL could not be read for an authorisation request.
384 // TRANS: %s is an avatar URL.
385 throw new Exception(sprintf(_('Cannot read avatar URL "%s".'),
388 if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG,
390 // TRANS: Exception thrown when avatar URL return an invalid image type for an authorisation request.
391 // TRANS: %s is an avatar URL.
392 throw new Exception(sprintf(_('Wrong image type for avatar URL '.