OpenIDPlugin.php 26.1 KB
Newer Older
1 2
<?php
/**
Evan Prodromou's avatar
Evan Prodromou committed
3
 * StatusNet, the distributed open-source microblogging tool
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * 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  Plugin
Evan Prodromou's avatar
Evan Prodromou committed
21 22
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
Evan Prodromou's avatar
Evan Prodromou committed
23
 * @author    Craig Andrews <candrews@integralblue.com>
24
 * @copyright 2009-2010 StatusNet, Inc.
25
 * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
26
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
Evan Prodromou's avatar
Evan Prodromou committed
27
 * @link      http://status.net/
28 29
 */

Evan Prodromou's avatar
Evan Prodromou committed
30
if (!defined('STATUSNET')) {
31 32 33 34 35 36 37 38 39
    exit(1);
}

/**
 * Plugin for OpenID authentication and identity
 *
 * This class enables consumer support for OpenID, the distributed authentication
 * and identity system.
 *
40 41
 * Depends on: WebFinger plugin for HostMeta-lookup (user@host format)
 *
42
 * @category Plugin
Evan Prodromou's avatar
Evan Prodromou committed
43 44
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
45 46
 * @author   Craig Andrews <candrews@integralblue.com>
 * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
47
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
Evan Prodromou's avatar
Evan Prodromou committed
48
 * @link     http://status.net/
49 50 51 52
 * @link     http://openid.net/
 */
class OpenIDPlugin extends Plugin
{
53 54 55 56 57 58 59 60 61 62 63 64
    // Plugin parameter: set true to disallow non-OpenID logins
    // If set, overrides the setting in database or $config['site']['openidonly']
    public $openidOnly = null;

    function initialize()
    {
        parent::initialize();
        if ($this->openidOnly !== null) {
            global $config;
            $config['site']['openidonly'] = (bool)$this->openidOnly;
        }
    }
65 66 67 68 69 70

    /**
     * Add OpenID-related paths to the router table
     *
     * Hook for RouterInitialized event.
     *
71
     * @param URLMapper $m URL mapper
72
     *
73 74
     * @return boolean hook return
     */
75
    public function onStartInitializeRouter(URLMapper $m)
76 77
    {
        $m->connect('main/openid', array('action' => 'openidlogin'));
78
        $m->connect('main/openidtrust', array('action' => 'openidtrust'));
79
        $m->connect('settings/openid', array('action' => 'openidsettings'));
80 81 82 83
        $m->connect('index.php?action=finishopenidlogin',
                    array('action' => 'finishopenidlogin'));
        $m->connect('index.php?action=finishaddopenid',
                    array('action' => 'finishaddopenid'));
84
        $m->connect('main/openidserver', array('action' => 'openidserver'));
85
        $m->connect('panel/openid', array('action' => 'openidadminpanel'));
86

87 88
        return true;
    }
89

90 91 92 93 94 95 96 97 98 99 100 101
    /**
     * In OpenID-only mode, disable paths for password stuff
     *
     * @param string $path     path to connect
     * @param array  $defaults path defaults
     * @param array  $rules    path rules
     * @param array  $result   unused
     *
     * @return boolean hook return
     */
    function onStartConnectPath(&$path, &$defaults, &$rules, &$result)
    {
102
        if (common_config('site', 'openidonly')) {
103 104 105 106 107 108 109 110
            // Note that we should not remove the login and register
            // actions. Lots of auth-related things link to them,
            // such as when visiting a private site without a session
            // or revalidating a remembered login for admin work.
            //
            // We take those two over with redirects to ourselves
            // over in onArgsInitialize().
            static $block = array('main/recoverpassword',
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
                                  'settings/password');

            if (in_array($path, $block)) {
                return false;
            }
        }

        return true;
    }

    /**
     * If we've been hit with password-login args, redirect
     *
     * @param array $args args (URL, Get, post)
     *
     * @return boolean hook return
     */
    function onArgsInitialize($args)
    {
130
        if (common_config('site', 'openidonly')) {
131 132 133 134 135 136 137
            if (array_key_exists('action', $args)) {
                $action = trim($args['action']);
                if (in_array($action, array('login', 'register'))) {
                    common_redirect(common_local_url('openidlogin'));
                } else if ($action == 'passwordsettings') {
                    common_redirect(common_local_url('openidsettings'));
                } else if ($action == 'recoverpassword') {
138 139
                    // TRANS: Client exception thrown when an action is not available.
                    throw new ClientException(_m('Unavailable action.'));
140 141 142 143 144 145
                }
            }
        }
        return true;
    }

146 147 148 149 150 151 152 153 154 155 156
    /**
     * Public XRDS output hook
     *
     * Puts the bits of code needed by some OpenID providers to show
     * we're good citizens.
     *
     * @param Action       $action         Action being executed
     * @param XMLOutputter &$xrdsOutputter Output channel
     *
     * @return boolean hook return
     */
157
    function onEndPublicXRDS(Action $action, &$xrdsOutputter)
158 159
    {
        $xrdsOutputter->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
160 161
                                                  'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
                                                  'version' => '2.0'));
162 163 164 165
        $xrdsOutputter->element('Type', null, 'xri://$xrds*simple');
        //consumer
        foreach (array('finishopenidlogin', 'finishaddopenid') as $finish) {
            $xrdsOutputter->showXrdsService(Auth_OpenID_RP_RETURN_TO_URL_TYPE,
166
                                            common_local_url($finish));
167 168 169
        }
        //provider
        $xrdsOutputter->showXrdsService('http://specs.openid.net/auth/2.0/server',
170 171 172 173
                                        common_local_url('openidserver'),
                                        null,
                                        null,
                                        'http://specs.openid.net/auth/2.0/identifier_select');
174 175 176
        $xrdsOutputter->elementEnd('XRD');
    }

177 178 179 180 181 182 183
    /**
     * If we're in OpenID-only mode, hide all the main menu except OpenID login.
     *
     * @param Action $action Action being run
     *
     * @return boolean hook return
     */
184 185
    function onStartPrimaryNav($action)
    {
186
        if (common_config('site', 'openidonly') && !common_logged_in()) {
187
            // TRANS: Tooltip for main menu option "Login"
188
            $tooltip = _m('TOOLTIP', 'Login to the site.');
189
            $action->menuItem(common_local_url('openidlogin'),
190
                              // TRANS: Main menu option when not logged in to log in
191 192 193 194 195 196 197
                              _m('MENU', 'Login'),
                              $tooltip,
                              false,
                              'nav_login');
            // TRANS: Tooltip for main menu option "Help"
            $tooltip = _m('TOOLTIP', 'Help me!');
            $action->menuItem(common_local_url('doc', array('title' => 'help')),
198
                              // TRANS: Main menu option for help on the StatusNet site
199 200 201 202 203 204
                              _m('MENU', 'Help'),
                              $tooltip,
                              false,
                              'nav_help');
            if (!common_config('site', 'private')) {
                // TRANS: Tooltip for main menu option "Search"
205
                $tooltip = _m('TOOLTIP', 'Search for people or text.');
206
                $action->menuItem(common_local_url('peoplesearch'),
207
                                  // TRANS: Main menu option when logged in or when the StatusNet instance is not private
208 209 210 211 212 213 214 215 216 217 218 219 220
                                  _m('MENU', 'Search'), $tooltip, false, 'nav_search');
            }
            Event::handle('EndPrimaryNav', array($action));
            return false;
        }
        return true;
    }

    /**
     * Menu for login
     *
     * If we're in openidOnly mode, we disable the menu for all other login.
     *
221
     * @param Action $action Action being executed
222 223 224
     *
     * @return boolean hook return
     */
225
    function onStartLoginGroupNav($action)
226
    {
227
        if (common_config('site', 'openidonly')) {
228 229 230 231 232 233 234 235 236 237
            $this->showOpenIDLoginTab($action);
            // Even though we replace this code, we
            // DON'T run the End* hook, to keep others from
            // adding tabs. Not nice, but.
            return false;
        }

        return true;
    }

238 239 240
    /**
     * Menu item for login
     *
241
     * @param Action $action Action being executed
242 243 244
     *
     * @return boolean hook return
     */
245
    function onEndLoginGroupNav($action)
246 247 248 249 250 251 252 253 254 255 256 257 258 259
    {
        $this->showOpenIDLoginTab($action);

        return true;
    }

    /**
     * Show menu item for login
     *
     * @param Action $action Action being executed
     *
     * @return void
     */
    function showOpenIDLoginTab($action)
260 261 262 263
    {
        $action_name = $action->trimmed('action');

        $action->menuItem(common_local_url('openidlogin'),
264 265 266
                          // TRANS: OpenID plugin menu item on site logon page.
                          _m('MENU', 'OpenID'),
                          // TRANS: OpenID plugin tooltip for logon menu item.
267
                          _m('Login or register with OpenID.'),
268
                          $action_name === 'openidlogin');
269
    }
270

271 272 273 274 275 276 277 278 279 280 281
    /**
     * Show menu item for password
     *
     * We hide it in openID-only mode
     *
     * @param Action $menu    Widget for menu
     * @param void   &$unused Unused value
     *
     * @return void
     */
    function onStartAccountSettingsPasswordMenuItem($menu, &$unused) {
282
        if (common_config('site', 'openidonly')) {
283 284
            return false;
        }
285 286
        return true;
    }
287

288
    /**
289
     * Menu item for OpenID settings
290
     *
291
     * @param Action $action Action being executed
292 293 294
     *
     * @return boolean hook return
     */
295
    function onEndAccountSettingsNav($action)
296 297 298 299
    {
        $action_name = $action->trimmed('action');

        $action->menuItem(common_local_url('openidsettings'),
300 301 302
                          // TRANS: OpenID plugin menu item on user settings page.
                          _m('MENU', 'OpenID'),
                          // TRANS: OpenID plugin tooltip for user settings menu item.
303
                          _m('Add or remove OpenIDs.'),
304 305 306 307
                          $action_name === 'openidsettings');

        return true;
    }
308

309 310 311 312 313 314 315 316 317
    /**
     * Autoloader
     *
     * Loads our classes if they're requested.
     *
     * @param string $cls Class requested
     *
     * @return boolean hook return
     */
318 319 320 321
    function onAutoload($cls)
    {
        switch ($cls)
        {
322 323 324 325
        case 'Auth_OpenID_TeamsExtension':
        case 'Auth_OpenID_TeamsRequest':
        case 'Auth_OpenID_TeamsResponse':
            require_once dirname(__FILE__) . '/extlib/teams-extension.php';
326
            return false;
327
        }
328 329

        return parent::onAutoload($cls);
330
    }
331

332 333 334 335 336 337 338 339 340 341
    /**
     * Login actions
     *
     * These actions should be visible even when the site is marked private
     *
     * @param Action  $action Action to show
     * @param boolean &$login Whether it's a login action
     *
     * @return boolean hook return
     */
342 343 344 345
    function onLoginAction($action, &$login)
    {
        switch ($action)
        {
346 347 348
        case 'openidlogin':
        case 'finishopenidlogin':
        case 'openidserver':
349 350
            $login = true;
            return false;
351
        default:
352 353 354
            return true;
        }
    }
355 356

    /**
357 358
     * We include a <meta> element linking to the webfinger resource page,
     * for OpenID client-side authentication.
359
     *
360 361
     * @param Action $action Action being shown
     *
362 363
     * @return void
     */
364
    function onEndShowHeadElements(Action $action)
365
    {
366
        if ($action instanceof ShowstreamAction) {
367 368 369
            $action->element('link', array('rel' => 'openid2.provider',
                                           'href' => common_local_url('openidserver')));
            $action->element('link', array('rel' => 'openid2.local_id',
mmn's avatar
mmn committed
370
                                           'href' => $action->getTarget()->getUrl()));
371 372 373
            $action->element('link', array('rel' => 'openid.server',
                                           'href' => common_local_url('openidserver')));
            $action->element('link', array('rel' => 'openid.delegate',
mmn's avatar
mmn committed
374
                                           'href' => $action->getTarget()->getUrl()));
375
        }
376 377 378 379 380

        if ($action instanceof SitestreamAction) {
            $action->element('meta', array('http-equiv' => 'X-XRDS-Location',
                                         'content' => common_local_url('publicxrds')));
        }
381
        return true;
382
    }
383 384 385 386

    /**
     * Redirect to OpenID login if they have an OpenID
     *
387 388 389
     * @param Action $action Action being executed
     * @param User   $user   User doing the action
     *
390 391 392 393
     * @return boolean whether to continue
     */
    function onRedirectToLogin($action, $user)
    {
394
        if (common_config('site', 'openidonly') || (!empty($user) && User_openid::hasOpenID($user->id))) {
395 396 397 398
            common_redirect(common_local_url('openidlogin'), 303);
        }
        return true;
    }
399

400 401 402 403 404 405 406
    /**
     * Show some extra instructions for using OpenID
     *
     * @param Action $action Action being executed
     *
     * @return boolean hook value
     */
407 408 409 410 411 412
    function onEndShowPageNotice($action)
    {
        $name = $action->trimmed('action');

        switch ($name)
        {
413
        case 'register':
414
            if (common_logged_in()) {
415 416 417 418
                // TRANS: Page notice for logged in users to try and get them to add an OpenID account to their StatusNet account.
                // TRANS: This message contains Markdown links in the form (description)[link].
                $instr = _m('(Have an [OpenID](http://openid.net/)? ' .
                  '[Add an OpenID to your account](%%action.openidsettings%%)!');
419
            } else {
420 421 422
                // TRANS: Page notice for anonymous users to try and get them to register with an OpenID account.
                // TRANS: This message contains Markdown links in the form (description)[link].
                $instr = _m('(Have an [OpenID](http://openid.net/)? ' .
423
                  'Try our [OpenID registration]'.
424
                  '(%%action.openidlogin%%)!)');
425
            }
426
            break;
427
        case 'login':
428 429 430
            // TRANS: Page notice on the login page to try and get them to log on with an OpenID account.
            // TRANS: This message contains Markdown links in the form (description)[link].
            $instr = _m('(Have an [OpenID](http://openid.net/)? ' .
431
              'Try our [OpenID login]'.
432
              '(%%action.openidlogin%%)!)');
433
            break;
434
        default:
435 436 437 438 439 440 441
            return true;
        }

        $output = common_markup_to_html($instr);
        $action->raw($output);
        return true;
    }
442

443 444 445 446 447 448 449 450
    /**
     * Load our document if requested
     *
     * @param string &$title  Title to fetch
     * @param string &$output HTML to output
     *
     * @return boolean hook value
     */
451 452
    function onStartLoadDoc(&$title, &$output)
    {
453
        if ($title == 'openid') {
454 455
            $filename = INSTALLDIR.'/plugins/OpenID/doc-src/openid';

456
            $c      = file_get_contents($filename);
457 458 459 460 461 462 463
            $output = common_markup_to_html($c);
            return false; // success!
        }

        return true;
    }

464 465 466 467 468 469 470 471
    /**
     * Add our document to the global menu
     *
     * @param string $title   Title being fetched
     * @param string &$output HTML being output
     *
     * @return boolean hook value
     */
472 473 474 475 476 477
    function onEndDocsMenu(&$items) {
        $items[] = array('doc', 
                         array('title' => 'openid'),
                         _m('MENU', 'OpenID'),
                         _('Logging in with OpenID'),
                         'nav_doc_openid');
478 479
        return true;
    }
480

481 482 483 484 485 486 487 488 489
    /**
     * Data definitions
     *
     * Assure that our data objects are available in the DB
     *
     * @return boolean hook value
     */
    function onCheckSchema()
    {
490
        $schema = Schema::get();
491 492
        $schema->ensureTable('user_openid', User_openid::schemaDef());
        $schema->ensureTable('user_openid_trustroot', User_openid_trustroot::schemaDef());
493 494
        $schema->ensureTable('user_openid_prefs', User_openid_prefs::schemaDef());

495 496 497 498 499 500
        /* These are used by JanRain OpenID library */

        $schema->ensureTable('oid_associations',
                             array(
                                 'fields' => array(
                                     'server_url' => array('type' => 'blob', 'not null' => true),
501
                                     'handle' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'default' => ''), // character set latin1,
502 503 504 505 506
                                     'secret' => array('type' => 'blob'),
                                     'issued' => array('type' => 'int'),
                                     'lifetime' => array('type' => 'int'),
                                     'assoc_type' => array('type' => 'varchar', 'length' => 64),
                                 ),
507
                                 'primary key' => array(array('server_url', 191), 'handle'),
508 509 510 511 512 513 514 515 516
                             ));
        $schema->ensureTable('oid_nonces',
                             array(
                                 'fields' => array(
                                     'server_url' => array('type' => 'varchar', 'length' => 2047),
                                     'timestamp' => array('type' => 'int'),
                                     'salt' => array('type' => 'char', 'length' => 40),
                                 ),
                                 'unique keys' => array(
517
                                     'oid_nonces_server_url_timestamp_salt_key' => array(array('server_url', 191), 'timestamp', 'salt'),
518 519 520
                                 ),
                             ));

521 522
        return true;
    }
523

524 525 526 527 528 529 530 531
    /**
     * Add our tables to be deleted when a user is deleted
     *
     * @param User  $user    User being deleted
     * @param array &$tables Array of table names
     *
     * @return boolean hook value
     */
532 533 534
    function onUserDeleteRelated($user, &$tables)
    {
        $tables[] = 'User_openid';
535
        $tables[] = 'User_openid_trustroot';
536 537
        return true;
    }
538

539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
    /**
     * Add an OpenID tab to the admin panel
     *
     * @param Widget $nav Admin panel nav
     *
     * @return boolean hook value
     */
    function onEndAdminPanelNav($nav)
    {
        if (AdminPanelAction::canAdmin('openid')) {

            $action_name = $nav->action->trimmed('action');

            $nav->out->menuItem(
                common_local_url('openidadminpanel'),
554 555 556
                // TRANS: OpenID configuration menu item.
                _m('MENU','OpenID'),
                // TRANS: Tooltip for OpenID configuration menu item.
557
                _m('OpenID configuration.'),
558 559 560 561 562 563 564 565
                $action_name == 'openidadminpanel',
                'nav_openid_admin_panel'
            );
        }

        return true;
    }

566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
    /**
     * Add OpenID information to the Account Management Control Document
     * Event supplied by the Account Manager plugin
     *
     * @param array &$amcd Array that expresses the AMCD
     *
     * @return boolean hook value
     */

    function onEndAccountManagementControlDocument(&$amcd)
    {
        $amcd['auth-methods']['openid'] = array(
            'connect' => array(
                'method' => 'POST',
                'path' => common_local_url('openidlogin'),
                'params' => array(
                    'identity' => 'openid_url'
                )
            )
        );
    }

588 589 590 591 592 593 594
    /**
     * Add our version information to output
     *
     * @param array &$versions Array of version-data arrays
     *
     * @return boolean hook value
     */
595
    function onPluginVersion(array &$versions)
596 597
    {
        $versions[] = array('name' => 'OpenID',
598
                            'version' => GNUSOCIAL_VERSION,
599 600 601
                            'author' => 'Evan Prodromou, Craig Andrews',
                            'homepage' => 'http://status.net/wiki/Plugin:OpenID',
                            'rawdescription' =>
602
                            // TRANS: Plugin description.
603 604 605
                            _m('Use <a href="http://openid.net/">OpenID</a> to login to the site.'));
        return true;
    }
Brion Vibber's avatar
Brion Vibber committed
606

607
    function onStartOAuthLoginForm($action, &$button)
Brion Vibber's avatar
Brion Vibber committed
608 609 610 611
    {
        if (common_config('site', 'openidonly')) {
            // Cancel the regular password login form, we won't need it.
            $this->showOAuthLoginForm($action);
612 613
            // TRANS: button label for OAuth authorization page when needing OpenID authentication first.
            $button = _m('BUTTON', 'Continue');
Brion Vibber's avatar
Brion Vibber committed
614 615 616 617 618 619 620 621 622 623 624 625 626 627
            return false;
        } else {
            // Leave the regular password login form in place.
            // We'll add an OpenID link at bottom...?
            return true;
        }
    }

    /**
     * @fixme merge with common code for main OpenID login form
     * @param HTMLOutputter $action
     */
    protected function showOAuthLoginForm($action)
    {
628 629
        $action->elementStart('fieldset');
        // TRANS: OpenID plugin logon form legend.
630
        $action->element('legend', null, _m('LEGEND','OpenID login'));
631

Brion Vibber's avatar
Brion Vibber committed
632 633 634 635 636
        $action->elementStart('ul', 'form_data');
        $action->elementStart('li');
        $provider = common_config('openid', 'trusted_provider');
        $appendUsername = common_config('openid', 'append_username');
        if ($provider) {
637
            // TRANS: Field label.
Brion Vibber's avatar
Brion Vibber committed
638 639 640 641 642 643 644 645
            $action->element('label', array(), _m('OpenID provider'));
            $action->element('span', array(), $provider);
            if ($appendUsername) {
                $action->element('input', array('id' => 'openid_username',
                                              'name' => 'openid_username',
                                              'style' => 'float: none'));
            }
            $action->element('p', 'form_guide',
646
                           // TRANS: Form guide.
Brion Vibber's avatar
Brion Vibber committed
647
                           ($appendUsername ? _m('Enter your username.') . ' ' : '') .
648
                           // TRANS: Form guide.
Brion Vibber's avatar
Brion Vibber committed
649 650 651 652 653 654 655
                           _m('You will be sent to the provider\'s site for authentication.'));
            $action->hidden('openid_url', $provider);
        } else {
            // TRANS: OpenID plugin logon form field label.
            $action->input('openid_url', _m('OpenID URL'),
                         '',
                        // TRANS: OpenID plugin logon form field instructions.
656
                         _m('Your OpenID URL.'));
Brion Vibber's avatar
Brion Vibber committed
657 658 659
        }
        $action->elementEnd('li');
        $action->elementEnd('ul');
660 661

        $action->elementEnd('fieldset');
Brion Vibber's avatar
Brion Vibber committed
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
    }

    /**
     * Handle a POST user credential check in apioauthauthorization.
     * If given an OpenID URL, we'll pass us over to the regular things
     * and then redirect back here on completion.
     *
     * @fixme merge with common code for main OpenID login form
     * @param HTMLOutputter $action
     */
    function onStartOAuthLoginCheck($action, &$user)
    {
        $provider = common_config('openid', 'trusted_provider');
        if ($provider) {
            $openid_url = $provider;
            if (common_config('openid', 'append_username')) {
                $openid_url .= $action->trimmed('openid_username');
            }
        } else {
            $openid_url = $action->trimmed('openid_url');
        }

        if ($openid_url) {
            require_once dirname(__FILE__) . '/openid.php';
            oid_assert_allowed($openid_url);

688
            $returnto = common_local_url(
689
                'ApiOAuthAuthorize',
690 691 692 693 694 695
                array(),
                array(
                    'oauth_token' => $action->arg('oauth_token'),
	            'mode'        => $action->arg('mode')
                )
            );
Brion Vibber's avatar
Brion Vibber committed
696 697 698 699 700 701 702 703 704 705 706 707 708 709
            common_set_returnto($returnto);

            // This will redirect if functional...
            $result = oid_authenticate($openid_url,
                                       'finishopenidlogin');
            if (is_string($result)) { # error message
                throw new ServerException($result);
            } else {
                exit(0);
            }
        }

        return true;
    }
710 711 712

    /**
     * Add link in user's XRD file to allow OpenID login.
713
     *
714 715 716 717
     * This link in the XRD should let users log in with their
     * Webfinger identity to services that support it. See
     * http://webfinger.org/login for an example.
     *
718 719
     * @param XML_XRD   $xrd    Currently-displaying resource descriptor
     * @param Profile   $target The profile that it's for
720
     *
721 722 723
     * @return boolean hook value (always true)
     */

724
    function onEndWebFingerProfileLinks(XML_XRD $xrd, Profile $target)
725
    {
726 727 728
        $xrd->links[] = new XML_XRD_Element_Link(
                            'http://specs.openid.net/auth/2.0/provider',
                            $target->profileurl);
729

730 731
        return true;
    }
732 733 734 735 736 737 738 739 740 741 742 743

    /**
     * Add links in the user's profile block to their OpenID URLs.
     *
     * @param Profile $profile The profile being shown
     * @param Array   &$links  Writeable array of arrays (href, text, image).
     *
     * @return boolean hook value (true)
     */
    
    function onOtherAccountProfiles($profile, &$links)
    {
744
        $prefs = User_openid_prefs::getKV('user_id', $profile->id);
745 746

        if (empty($prefs) || !$prefs->hide_profile_link) {
747

748
            $oid = new User_openid();
749

750 751 752 753 754 755 756 757
            $oid->user_id = $profile->id;

            if ($oid->find()) {
                while ($oid->fetch()) {
                    $links[] = array('href' => $oid->display,
                                     'text' => _('OpenID'),
                                     'image' => $this->path("icons/openid-16x16.gif"));
                }
758 759 760 761 762
            }
        }

        return true;
    }
763
}