gnu-social/plugins/OpenID/OpenIDPlugin.php
Mikael Nordfeldth 3a7261f70a IMPORTANT: Making prev. Memcached_DataObject working again with schemaDef
Lots of the Memcached_DataObject classes stopped working when upgraded to
Managed_DataObject because they lacked schemaDef().

I have _hopefully_ made it so that all the references to the table uses
each class' schemaDef, rather than the more manual ColumnDef stuff. Not
all plugins have been tested thoroughly yet.

NOTE: This is applied with getKV calls instead of staticGet, as it was
important for PHP Strict Standards compliance to avoid calling the non-
static functions statically. (unfortunately DB and DB_DataObject still do
this within themselves...)
2013-08-21 09:48:42 +02:00

833 lines
28 KiB
PHP

<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* 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
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Craig Andrews <candrews@integralblue.com>
* @copyright 2009-2010 StatusNet, Inc.
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @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);
}
/**
* Plugin for OpenID authentication and identity
*
* This class enables consumer support for OpenID, the distributed authentication
* and identity system.
*
* @category Plugin
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Craig Andrews <candrews@integralblue.com>
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
* @link http://openid.net/
*/
class OpenIDPlugin extends Plugin
{
// 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;
}
}
/**
* Add OpenID-related paths to the router table
*
* Hook for RouterInitialized event.
*
* @param Net_URL_Mapper $m URL mapper
*
* @return boolean hook return
*/
function onStartInitializeRouter($m)
{
$m->connect('main/openid', array('action' => 'openidlogin'));
$m->connect('main/openidtrust', array('action' => 'openidtrust'));
$m->connect('settings/openid', array('action' => 'openidsettings'));
$m->connect('index.php?action=finishopenidlogin',
array('action' => 'finishopenidlogin'));
$m->connect('index.php?action=finishaddopenid',
array('action' => 'finishaddopenid'));
$m->connect('main/openidserver', array('action' => 'openidserver'));
$m->connect('panel/openid', array('action' => 'openidadminpanel'));
return true;
}
/**
* 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)
{
if (common_config('site', 'openidonly')) {
// 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',
'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)
{
if (common_config('site', 'openidonly')) {
if (array_key_exists('action', $args)) {
$action = trim($args['action']);
if (in_array($action, array('login', 'register'))) {
common_redirect(common_local_url('openidlogin'));
exit(0);
} else if ($action == 'passwordsettings') {
common_redirect(common_local_url('openidsettings'));
exit(0);
} else if ($action == 'recoverpassword') {
// TRANS: Client exception thrown when an action is not available.
throw new ClientException(_m('Unavailable action.'));
}
}
}
return true;
}
/**
* 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
*/
function onEndPublicXRDS($action, &$xrdsOutputter)
{
$xrdsOutputter->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
'version' => '2.0'));
$xrdsOutputter->element('Type', null, 'xri://$xrds*simple');
//consumer
foreach (array('finishopenidlogin', 'finishaddopenid') as $finish) {
$xrdsOutputter->showXrdsService(Auth_OpenID_RP_RETURN_TO_URL_TYPE,
common_local_url($finish));
}
//provider
$xrdsOutputter->showXrdsService('http://specs.openid.net/auth/2.0/server',
common_local_url('openidserver'),
null,
null,
'http://specs.openid.net/auth/2.0/identifier_select');
$xrdsOutputter->elementEnd('XRD');
}
/**
* User XRDS output hook
*
* Puts the bits of code needed to discover OpenID endpoints.
*
* @param Action $action Action being executed
* @param XMLOutputter &$xrdsOutputter Output channel
*
* @return boolean hook return
*/
function onEndUserXRDS($action, &$xrdsOutputter)
{
$xrdsOutputter->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
'xml:id' => 'openid',
'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
'version' => '2.0'));
$xrdsOutputter->element('Type', null, 'xri://$xrds*simple');
//consumer
$xrdsOutputter->showXrdsService('http://specs.openid.net/auth/2.0/return_to',
common_local_url('finishopenidlogin'));
//provider
$xrdsOutputter->showXrdsService('http://specs.openid.net/auth/2.0/signon',
common_local_url('openidserver'),
null,
null,
common_profile_url($action->user->nickname));
$xrdsOutputter->elementEnd('XRD');
}
/**
* 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
*/
function onStartPrimaryNav($action)
{
if (common_config('site', 'openidonly') && !common_logged_in()) {
// TRANS: Tooltip for main menu option "Login"
$tooltip = _m('TOOLTIP', 'Login to the site.');
$action->menuItem(common_local_url('openidlogin'),
// TRANS: Main menu option when not logged in to log in
_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')),
// TRANS: Main menu option for help on the StatusNet site
_m('MENU', 'Help'),
$tooltip,
false,
'nav_help');
if (!common_config('site', 'private')) {
// TRANS: Tooltip for main menu option "Search"
$tooltip = _m('TOOLTIP', 'Search for people or text.');
$action->menuItem(common_local_url('peoplesearch'),
// TRANS: Main menu option when logged in or when the StatusNet instance is not private
_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.
*
* @param Action $action Action being executed
*
* @return boolean hook return
*/
function onStartLoginGroupNav($action)
{
if (common_config('site', 'openidonly')) {
$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;
}
/**
* Menu item for login
*
* @param Action $action Action being executed
*
* @return boolean hook return
*/
function onEndLoginGroupNav($action)
{
$this->showOpenIDLoginTab($action);
return true;
}
/**
* Show menu item for login
*
* @param Action $action Action being executed
*
* @return void
*/
function showOpenIDLoginTab($action)
{
$action_name = $action->trimmed('action');
$action->menuItem(common_local_url('openidlogin'),
// TRANS: OpenID plugin menu item on site logon page.
_m('MENU', 'OpenID'),
// TRANS: OpenID plugin tooltip for logon menu item.
_m('Login or register with OpenID.'),
$action_name === 'openidlogin');
}
/**
* 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) {
if (common_config('site', 'openidonly')) {
return false;
}
return true;
}
/**
* Menu item for OpenID settings
*
* @param Action $action Action being executed
*
* @return boolean hook return
*/
function onEndAccountSettingsNav($action)
{
$action_name = $action->trimmed('action');
$action->menuItem(common_local_url('openidsettings'),
// TRANS: OpenID plugin menu item on user settings page.
_m('MENU', 'OpenID'),
// TRANS: OpenID plugin tooltip for user settings menu item.
_m('Add or remove OpenIDs.'),
$action_name === 'openidsettings');
return true;
}
/**
* Autoloader
*
* Loads our classes if they're requested.
*
* @param string $cls Class requested
*
* @return boolean hook return
*/
function onAutoload($cls)
{
switch ($cls)
{
case 'OpenidloginAction':
case 'FinishopenidloginAction':
case 'FinishaddopenidAction':
case 'XrdsAction':
case 'PublicxrdsAction':
case 'OpenidsettingsAction':
case 'OpenidserverAction':
case 'OpenidtrustAction':
case 'OpenidadminpanelAction':
require_once dirname(__FILE__) . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
return false;
case 'User_openid':
case 'User_openid_prefs':
case 'User_openid_trustroot':
require_once dirname(__FILE__) . '/' . $cls . '.php';
return false;
case 'Auth_OpenID_TeamsExtension':
case 'Auth_OpenID_TeamsRequest':
case 'Auth_OpenID_TeamsResponse':
require_once dirname(__FILE__) . '/extlib/teams-extension.php';
return false;
default:
return true;
}
}
/**
* Sensitive actions
*
* These actions should use https when SSL support is 'sometimes'
*
* @param Action $action Action to form an URL for
* @param boolean &$ssl Whether to mark it for SSL
*
* @return boolean hook return
*/
function onSensitiveAction($action, &$ssl)
{
switch ($action)
{
case 'finishopenidlogin':
case 'finishaddopenid':
$ssl = true;
return false;
default:
return true;
}
}
/**
* 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
*/
function onLoginAction($action, &$login)
{
switch ($action)
{
case 'openidlogin':
case 'finishopenidlogin':
case 'openidserver':
$login = true;
return false;
default:
return true;
}
}
/**
* We include a <meta> element linking to the userxrds page, for OpenID
* client-side authentication.
*
* @param Action $action Action being shown
*
* @return void
*/
function onEndShowHeadElements($action)
{
if ($action instanceof ShowstreamAction) {
$action->element('link', array('rel' => 'openid2.provider',
'href' => common_local_url('openidserver')));
$action->element('link', array('rel' => 'openid2.local_id',
'href' => $action->profile->profileurl));
$action->element('link', array('rel' => 'openid.server',
'href' => common_local_url('openidserver')));
$action->element('link', array('rel' => 'openid.delegate',
'href' => $action->profile->profileurl));
}
return true;
}
/**
* Redirect to OpenID login if they have an OpenID
*
* @param Action $action Action being executed
* @param User $user User doing the action
*
* @return boolean whether to continue
*/
function onRedirectToLogin($action, $user)
{
if (common_config('site', 'openid_only') || (!empty($user) && User_openid::hasOpenID($user->id))) {
common_redirect(common_local_url('openidlogin'), 303);
return false;
}
return true;
}
/**
* Show some extra instructions for using OpenID
*
* @param Action $action Action being executed
*
* @return boolean hook value
*/
function onEndShowPageNotice($action)
{
$name = $action->trimmed('action');
switch ($name)
{
case 'register':
if (common_logged_in()) {
// 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%%)!');
} else {
// 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/)? ' .
'Try our [OpenID registration]'.
'(%%action.openidlogin%%)!)');
}
break;
case 'login':
// 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/)? ' .
'Try our [OpenID login]'.
'(%%action.openidlogin%%)!)');
break;
default:
return true;
}
$output = common_markup_to_html($instr);
$action->raw($output);
return true;
}
/**
* Load our document if requested
*
* @param string &$title Title to fetch
* @param string &$output HTML to output
*
* @return boolean hook value
*/
function onStartLoadDoc(&$title, &$output)
{
if ($title == 'openid') {
$filename = INSTALLDIR.'/plugins/OpenID/doc-src/openid';
$c = file_get_contents($filename);
$output = common_markup_to_html($c);
return false; // success!
}
return true;
}
/**
* Add our document to the global menu
*
* @param string $title Title being fetched
* @param string &$output HTML being output
*
* @return boolean hook value
*/
function onEndDocsMenu(&$items) {
$items[] = array('doc',
array('title' => 'openid'),
_m('MENU', 'OpenID'),
_('Logging in with OpenID'),
'nav_doc_openid');
return true;
}
/**
* Data definitions
*
* Assure that our data objects are available in the DB
*
* @return boolean hook value
*/
function onCheckSchema()
{
$schema = Schema::get();
$schema->ensureTable('user_openid', User_openid::schemaDef());
$schema->ensureTable('user_openid_trustroot', User_openid_trustroot::schemaDef());
$schema->ensureTable('user_openid_prefs', User_openid_prefs::schemaDef());
/* These are used by JanRain OpenID library */
$schema->ensureTable('oid_associations',
array(
'fields' => array(
'server_url' => array('type' => 'blob', 'not null' => true),
'handle' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'default' => ''), // character set latin1,
'secret' => array('type' => 'blob'),
'issued' => array('type' => 'int'),
'lifetime' => array('type' => 'int'),
'assoc_type' => array('type' => 'varchar', 'length' => 64),
),
'primary key' => array(array('server_url', 255), 'handle'),
));
$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(
'oid_nonces_server_url_timestamp_salt_key' => array(array('server_url', 255), 'timestamp', 'salt'),
),
));
return true;
}
/**
* 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
*/
function onUserDeleteRelated($user, &$tables)
{
$tables[] = 'User_openid';
$tables[] = 'User_openid_trustroot';
return true;
}
/**
* 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'),
// TRANS: OpenID configuration menu item.
_m('MENU','OpenID'),
// TRANS: Tooltip for OpenID configuration menu item.
_m('OpenID configuration.'),
$action_name == 'openidadminpanel',
'nav_openid_admin_panel'
);
}
return true;
}
/**
* 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'
)
)
);
}
/**
* Add our version information to output
*
* @param array &$versions Array of version-data arrays
*
* @return boolean hook value
*/
function onPluginVersion(&$versions)
{
$versions[] = array('name' => 'OpenID',
'version' => STATUSNET_VERSION,
'author' => 'Evan Prodromou, Craig Andrews',
'homepage' => 'http://status.net/wiki/Plugin:OpenID',
'rawdescription' =>
// TRANS: Plugin description.
_m('Use <a href="http://openid.net/">OpenID</a> to login to the site.'));
return true;
}
function onStartOAuthLoginForm($action, &$button)
{
if (common_config('site', 'openidonly')) {
// Cancel the regular password login form, we won't need it.
$this->showOAuthLoginForm($action);
// TRANS: button label for OAuth authorization page when needing OpenID authentication first.
$button = _m('BUTTON', 'Continue');
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)
{
$action->elementStart('fieldset');
// TRANS: OpenID plugin logon form legend.
$action->element('legend', null, _m('LEGEND','OpenID login'));
$action->elementStart('ul', 'form_data');
$action->elementStart('li');
$provider = common_config('openid', 'trusted_provider');
$appendUsername = common_config('openid', 'append_username');
if ($provider) {
// TRANS: Field label.
$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',
// TRANS: Form guide.
($appendUsername ? _m('Enter your username.') . ' ' : '') .
// TRANS: Form guide.
_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.
_m('Your OpenID URL.'));
}
$action->elementEnd('li');
$action->elementEnd('ul');
$action->elementEnd('fieldset');
}
/**
* 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);
$returnto = common_local_url(
'ApiOauthAuthorize',
array(),
array(
'oauth_token' => $action->arg('oauth_token'),
'mode' => $action->arg('mode')
)
);
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;
}
/**
* Add link in user's XRD file to allow OpenID login.
*
* 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.
*
* @param XRD &$xrd Currently-displaying XRD object
* @param User $user The user that it's for
*
* @return boolean hook value (always true)
*/
function onEndXrdActionLinks(&$xrd, $user)
{
$profile = $user->getProfile();
if (!empty($profile)) {
$xrd->links[] = array('rel' => 'http://specs.openid.net/auth/2.0/provider',
'href' => $profile->profileurl);
}
return true;
}
/**
* 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)
{
$prefs = User_openid_prefs::getKV('user_id', $profile->id);
if (empty($prefs) || !$prefs->hide_profile_link) {
$oid = new User_openid();
$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"));
}
}
}
return true;
}
}