Commit cfef0ff9 authored by Aaron Wells's avatar Aaron Wells

Bug 1533377: Browserid end-of-life migration script

This patch removes all authentication functionality
from the browserid auth plugin. It adds a script,
accessible through the plugin's configuration page,
to migrate user accounts from browserid to
internal auth.

Also includes changes to allow users to be searched
by authtype, and to prevent non-useable plugins
from being enabled on the plugin config page.

Change-Id: I4e8bd9fc4d2fb2ccaa1845fda533c9373ec251bd
behatnotneeded: Can't test with behat
parent 86f29b70
......@@ -49,8 +49,12 @@ foreach (array_keys($plugins) as $plugin) {
'disableable' => call_static_method($classname, 'can_be_disabled'),
'deprecated' => call_static_method($classname, 'is_deprecated'),
'name' => call_static_method($classname, 'get_plugin_display_name'),
'enableable' => call_static_method($classname, 'is_usable')
);
if ($plugins[$plugin]['installed'][$key]['disableable'] || !$i->active) {
if (
($i->active && $plugins[$plugin]['installed'][$key]['disableable'])
|| (!$i->active && $plugins[$plugin]['installed'][$key]['enableable'])
){
$plugins[$plugin]['installed'][$key]['activateform'] = activate_plugin_form($plugin, $i);
}
if ($plugin == 'artefact') {
......
......@@ -29,6 +29,7 @@ $search = (object) array(
'loggedin' => param_alpha('loggedin', 'any'),
'loggedindate' => param_variable('loggedindate', strftime(get_string('strftimedatetimeshort'))),
'duplicateemail' => param_boolean('duplicateemail', false),
'authname' => param_alpha('authname', null),
);
$offset = param_integer('offset', 0);
......
......@@ -14,15 +14,14 @@ defined('INTERNAL') || die();
$string['browserid'] = 'Persona';
$string['title'] = 'Persona';
$string['description'] = 'Authenticate using Persona';
$string['notusable'] = 'Please install the PHP cURL extension and check the connection to the Persona verifier';
$string['notusable'] = 'Discontinued';
$string['badassertion'] = 'The given Persona assertion is not valid: %s';
$string['badverification'] = 'Mahara did not receive valid JSON output from the Persona verifier.';
$string['login'] = 'Persona';
$string['register'] = 'Register with Persona';
$string['missingassertion'] = 'Persona did not return an alpha-numeric assertion.';
$string['deprecatedmsg'] = "As of 30 November 2016, <a href=\"https://wiki.mozilla.org/Identity/Persona_Shutdown_Guidelines_for_Reliers\">Mozilla is discontinuing the Persona
authentication service</a>. This plugin is a placeholder to aid in migrating existing Persona accounts from Persona to internal authentication.";
$string['nobrowseridinstances'] = 'This site has no Persona auth instances, so no action needs to be taken.';
$string['emailalreadyclaimed'] = "Another user account has already claimed the email address '%s'.";
$string['emailclaimedasusername'] = "Another user account has already claimed the email address '%s' as a username.";
$string['browseridnotenabled'] = "The Persona authentication plugin is not enabled in any active institution.";
$string['emailnotfound'] = "A user account with an email address of '%s' was not found in any of the institutions where Persona is enabled.";
$string['institutioncolumn'] = 'Institution';
$string['numuserscolumn'] = 'Number of active Persona users';
$string['migratetitle'] = 'Auto-migrate Persona users';
$string['migratedesc'] = 'Automatically move all Persona users to internal auth, and delete all Persona auth instances.';
\ No newline at end of file
<!-- @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later -->
<!-- @copyright For copyright information on Mahara, please see the README file distributed with this software. -->
<h3>We auto-create users</h3>
<p>Users that successfully authenticate but are not users of this system yet
will have an account created automatically.</p>
<p>Their username will be their email address.</p>
<!-- @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later -->
<!-- @copyright For copyright information on Mahara, please see the README file distributed with this software. -->
<h3>Auto-migrate Persona users</h3>
<p>Mozilla is <a href="https://wiki.mozilla.org/Identity/Persona_Shutdown_Guidelines_for_Reliers">discontinuing the Persona authentication service</a> as of 30 November, 2016.
If your site is using Persona, you will need to migrate those users to a different authentication method. This plugin provides a basic option to change all Persona-based user
accounts on your site, to the Mahara "internal" authentication method.</p>
<p>Selecting "Yes" and saving this form, will activate the migration script, and do the following:</p>
<ol>
<li>All Persona auth instances on the site will be deleted.</li>
<li>All users who are on Persona authentication, will be switched to the Internal auth instance for their institution.</li>
<li>If their institution has no Internal auth instance, one will be created.</li>
<li>These users will <b>not</b> have a password set. They will need to use the "Forgot password" link (and their Persona email address) to set an initial Mahara password.</li>
<li>The users' usernames will be unchanged.</li>
</ol>
<p><b>Note:</b> Users will not receive any notification that their authentication method has changed. You may wish to put a message on your site's logged-out homepage to explain
to former Persona users that they should use the "Forgot password" link, and their Persona email address, to set a new password and access their Mahara account.</p>
This diff is collapsed.
<?php
/**
*
* @package mahara
* @subpackage auth-browserid
* @author Francois Marier <francois@catalyst.net.nz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
define('INTERNAL', 1);
define('PUBLIC', 1);
require('../../init.php');
safe_require('auth', 'browserid');
if (empty($_SESSION['browseridexpires']) || time() >= $_SESSION['browseridexpires']) {
$assertion = param_variable('assertion', null);
if (!$assertion) {
throw new AuthInstanceException(get_string('missingassertion','auth.browserid'));
}
// Send the assertion to the verification service
$request = array(
CURLOPT_URL => PluginAuthBrowserid::BROWSERID_VERIFIER_URL,
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => 'assertion='.urlencode($assertion).'&audience='.get_audience(),
);
$response = mahara_http_request($request);
if (empty($response->data)) {
throw new AuthInstanceException(get_string('badverification','auth.browserid'));
}
$jsondata = json_decode($response->data);
if (empty($jsondata)) {
throw new AuthInstanceException(get_string('badverification','auth.browserid'));
}
if ($jsondata->status != 'okay') {
throw new AuthInstanceException(get_string('badassertion','auth.browserid', htmlspecialchars($jsondata->reason)));
}
$SESSION->set('browseridexpires', $jsondata->expires/1000);
$SESSION->set('browseridemail', $jsondata->email);
}
// Not using $USER->get('sesskey') for this because when we printed the browserid setup stuff
// in auth/browserid/lib.php, $USER isn't set up yet.
$sesskey = param_variable('sesskey', false);
if ($sesskey && $sesskey == $SESSION->get('browseridsesskey')) {
$returnurl = param_variable('returnurl', '/');
$SESSION->clear('browseridsesskey');
}
else {
$returnurl = '/';
}
$USER = new BrowserIDUser();
$USER->login($_SESSION['browseridemail']);
unset($SESSION->browseridexpires);
unset($SESSION->browseridemail);
redirect($returnurl);
function get_audience() {
$url = parse_url(get_config('wwwroot'));
if (!isset($url['port']) and 'http' == $url['scheme']) {
$port = 80;
}
else if (!isset($url['port']) and 'https' == $url['scheme']) {
$port = 443;
}
else if (isset($url['port'])) {
$port = $url['port'];
}
else {
log_debug('Persona: cannot decipher the value of wwwroot');
return '';
}
return $url['scheme'] . '://' .$url['host'] . ':' . $port;
}
......@@ -11,7 +11,7 @@
defined('INTERNAL') || die();
$config = new StdClass;
$config->version = 2016020900;
$config->release = '1.0.1';
$config->requires_config = 1;
$config->version = 2016092600;
$config->release = '0.0.0';
$config->requires_config = 0;
$config->requires_parent = 0;
......@@ -2482,15 +2482,10 @@ function auth_register_submit(Pieform $form, $values) {
$SESSION->set('registeredokawaiting', true);
}
else {
if (isset($values['authtype']) && $values['authtype'] == 'browserid') {
redirect('/register.php?key='.$values['key']);
}
else {
email_user($user, null,
get_string('registeredemailsubject', 'auth.internal', get_config('sitename')),
get_string('registeredemailmessagetext', 'auth.internal', $values['firstname'], get_config('sitename'), get_config('wwwroot'), $values['key'], get_config('sitename')),
get_string('registeredemailmessagehtml', 'auth.internal', $values['firstname'], get_config('sitename'), get_config('wwwroot'), $values['key'], get_config('wwwroot'), $values['key'], get_config('sitename')));
}
email_user($user, null,
get_string('registeredemailsubject', 'auth.internal', get_config('sitename')),
get_string('registeredemailmessagetext', 'auth.internal', $values['firstname'], get_config('sitename'), get_config('wwwroot'), $values['key'], get_config('sitename')),
get_string('registeredemailmessagehtml', 'auth.internal', $values['firstname'], get_config('sitename'), get_config('wwwroot'), $values['key'], get_config('wwwroot'), $values['key'], get_config('sitename')));
// Add a marker in the session to say that the user has registered
$SESSION->set('registered', true);
}
......@@ -2539,14 +2534,6 @@ class PluginAuth extends Plugin {
return $subscriptions;
}
/**
* Can be overridden by plugins to assert when they are able to be used.
* For example, a plugin might check that a certain PHP extension is loaded
*/
public static function is_usable() {
return true;
}
/**
* This function returns an array of menu items
* to be displayed
......
......@@ -2300,6 +2300,14 @@ abstract class Plugin implements IPlugin {
return true;
}
/**
* Can be overridden by plugins to assert when they are able to be used.
* For example, a plugin might check that a certain PHP extension is loaded
*/
public static function is_usable() {
return true;
}
/**
* Check whether this plugin is okay to be installed.
*
......
......@@ -210,6 +210,14 @@ function get_admin_user_search_results($search, $offset, $limit) {
}
}
if (!empty($search->authname)) {
$constraints[] = array(
'field' => 'authname',
'type' => 'equals',
'string' => $search->authname
);
}
if (!empty($search->f)) {
$constraints[] = array('field' => 'firstname',
'type' => 'starts',
......@@ -389,7 +397,7 @@ function get_admin_user_search_results($search, $offset, $limit) {
function build_admin_user_search_results($search, $offset, $limit) {
global $USER, $THEME;
$wantedparams = array('query', 'f', 'l', 'sortby', 'sortdir', 'loggedin', 'loggedindate', 'duplicateemail', 'institution');
$wantedparams = array('query', 'f', 'l', 'sortby', 'sortdir', 'loggedin', 'loggedindate', 'duplicateemail', 'institution', 'authname');
$params = array();
foreach ($search as $k => $v) {
if (!in_array($k, $wantedparams)) {
......
......@@ -461,53 +461,58 @@ class PluginSearchInternal extends PluginSearch {
$firstcols = 'u.id';
if (!empty($constraints)) {
foreach ($constraints as $f) {
if ($f['field'] == 'institution') {
if ($f['string'] == 'mahara') {
$where .= ' AND u.id NOT IN (SELECT usr FROM {usr_institution})';
}
else {
$where .= '
AND u.id IN (
SELECT usr FROM {usr_institution} WHERE institution '
. PluginSearchInternal::match_expression($f['type'], $f['string'], $values, $ilike) . '
)';
}
}
else if ($f['field'] == 'duplicateemail') {
if (!empty($f['string'])) {
$where .= '
AND u.id IN (
SELECT owner
FROM {artefact}
WHERE id IN (' . join(',', array_map('db_quote', $f['string'])) . ')
)';
}
else {
// No duplicate email is found, return empty list
$where .= ' AND FALSE';
}
}
else if ($f['field'] == 'exportqueue') {
$firstcols = 'e.id AS eid,
(SELECT case WHEN e.starttime IS NOT NULL THEN ' . db_format_tsfield('e.starttime', false) . ' ELSE ' . db_format_tsfield('e.ctime', false) . ' END) AS status,
' . $firstcols;
$join .= 'JOIN {export_queue} e ON e.usr = u.id ';
$where .= ' AND u.id'
. PluginSearchInternal::match_expression($f['type'], $f['string'], $values, $ilike);
}
else if ($f['field'] == 'archivesubmissions') {
$firstcols = 'e.id AS eid, a.group,
(SELECT name FROM {group} WHERE id = a.group) AS submittedto,
(SELECT case WHEN a.externalid IS NOT NULL THEN a.externalid ELSE CAST(e.id AS char) END) AS specialid,
e.filetitle, e.filename, e.filepath, ' . db_format_tsfield('e.ctime', 'archivectime') . ', ' . $firstcols;
$join .= 'JOIN {export_archive} e ON e.usr = u.id ';
$join .= 'JOIN {archived_submissions} a ON a.archiveid = e.id ';
$where .= ' AND u.id'
. PluginSearchInternal::match_expression($f['type'], $f['string'], $values, $ilike);
}
else {
$where .= ' AND u.' . $f['field']
. PluginSearchInternal::match_expression($f['type'], $f['string'], $values, $ilike);
switch ($f['field']) {
case 'institution':
if ($f['string'] == 'mahara') {
$where .= ' AND u.id NOT IN (SELECT usr FROM {usr_institution})';
}
else {
$where .= '
AND u.id IN (
SELECT usr FROM {usr_institution} WHERE institution '
. PluginSearchInternal::match_expression($f['type'], $f['string'], $values, $ilike) . '
)';
}
break;
case 'duplicateemail':
if (!empty($f['string'])) {
$where .= '
AND u.id IN (
SELECT owner
FROM {artefact}
WHERE id IN (' . join(',', array_map('db_quote', $f['string'])) . ')
)';
}
else {
// No duplicate email is found, return empty list
$where .= ' AND FALSE';
}
break;
case 'exportqueue':
$firstcols = 'e.id AS eid,
(SELECT case WHEN e.starttime IS NOT NULL THEN ' . db_format_tsfield('e.starttime', false) . ' ELSE ' . db_format_tsfield('e.ctime', false) . ' END) AS status,
' . $firstcols;
$join .= 'JOIN {export_queue} e ON e.usr = u.id ';
$where .= ' AND u.id'
. PluginSearchInternal::match_expression($f['type'], $f['string'], $values, $ilike);
break;
case 'archivesubmissions':
$firstcols = 'e.id AS eid, a.group,
(SELECT name FROM {group} WHERE id = a.group) AS submittedto,
(SELECT case WHEN a.externalid IS NOT NULL THEN a.externalid ELSE CAST(e.id AS char) END) AS specialid,
e.filetitle, e.filename, e.filepath, ' . db_format_tsfield('e.ctime', 'archivectime') . ', ' . $firstcols;
$join .= 'JOIN {export_archive} e ON e.usr = u.id ';
$join .= 'JOIN {archived_submissions} a ON a.archiveid = e.id ';
$where .= ' AND u.id'
. PluginSearchInternal::match_expression($f['type'], $f['string'], $values, $ilike);
break;
case 'authname':
$join .= 'JOIN {auth_instance} ai ON ai.id = u.authinstance ';
$where .= ' AND ai.authname ' . PluginSearchInternal::match_expression($f['type'], $f['string'], $values, $ilike);
break;
default:
$where .= ' AND u.' . $f['field']
. PluginSearchInternal::match_expression($f['type'], $f['string'], $values, $ilike);
}
}
}
......
<table class="table fullwidth table-padded">
<thead>
<tr>
<th>{str tag='institutioncolumn' section='auth.browserid'}</th>
<th>{str tag='numuserscolumn' section='auth.browserid'}</th>
</tr>
</thead>
<tbody>
{foreach from=$instances item=item}
<tr>
<td>
<h3 class="title">
<a href="{$WWWROOT}admin/users/institutions.php?i={$item->name}">{$item->displayname}</a>
</h3>
</td>
<td>
{if $item->numusers}
<a href="{$WWWROOT}admin/users/search.php?institution={$item->name}&authname=browserid">{str tag='nusers' section='mahara' arg1=$item->numusers}</a>
{else}
{str tag='nusers' section='mahara' arg1=0}
{/if}
</td>
</tr>
{/foreach}
</tbody>
</table>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment