Skip to content

Commit

Permalink
feat(admin): added a site setting to require admin approval of accounts
Browse files Browse the repository at this point in the history
After registering an account a site administrator needs to approve the
account.
  • Loading branch information
jeabakker committed Sep 13, 2019
1 parent 712ba77 commit 2882da6
Show file tree
Hide file tree
Showing 13 changed files with 412 additions and 5 deletions.
12 changes: 12 additions & 0 deletions actions/admin/site/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@
$allow_registration = ('on' === get_input('allow_registration', false));
elgg_save_config('allow_registration', $allow_registration);

// require admin validation for new users?
$require_admin_validation = ('on' === get_input('require_admin_validation', false));
elgg_save_config('require_admin_validation', $require_admin_validation);

// notify admins about pending validation
$admin_validation_notification = get_input('admin_validation_notification');
if (empty($admin_validation_notification)) {
elgg_remove_config('admin_validation_notification');
} else {
elgg_save_config('admin_validation_notification', $admin_validation_notification);
}

// setup walled garden
$walled_garden = ('on' === get_input('walled_garden', false));
elgg_save_config('walled_garden', $walled_garden);
Expand Down
12 changes: 11 additions & 1 deletion docs/admin/uservalidation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,21 @@ Listing of unvalidated users
============================

In the Admin section of the website is a list of all unvalidated users. Some actions can be taken on the users, like delete them from the system
or validate them.
or validate them.

Plugins have the option to add additional features to this list.

.. seealso::

An example of this is the :doc:`/plugins/uservalidationbyemail` plugin which doesn't allow users onto the website until their e-mail address
is validated.

Require admin validation
========================

In the Site settings under the Users section there is a setting which can be enabled to require admin validation of a new user account before
the user can use their account. After registration the user gets notified that their account is awaiting validation by an administrator.

Site administrators can receive an e-mail notification that there are users awaiting validation.

After validation the user is notified that they can use their account.
1 change: 1 addition & 0 deletions engine/classes/Elgg/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
* @property array $redis_servers
* @property string[] $registered_entities
* @property bool $remove_branding Is Elgg branding disabled
* @property bool $require_admin_validation
* @property bool $security_disable_password_autocomplete
* @property bool $security_email_require_password
* @property bool $security_notify_admins
Expand Down
1 change: 1 addition & 0 deletions engine/classes/ElggInstaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,7 @@ protected function saveSiteSettings($submissionVars) {
'language' => 'en',
'default_access' => $submissionVars['siteaccess'],
'allow_registration' => false,
'require_admin_validation' => false,
'walled_garden' => false,
'allow_user_default_access' => '',
'default_limit' => 10,
Expand Down
230 changes: 230 additions & 0 deletions engine/lib/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

use Elgg\Menu\MenuItems;
use Elgg\Database\QueryBuilder;
use Elgg\Http\ResponseBuilder;

/**
* Get the admin users
Expand Down Expand Up @@ -185,6 +186,15 @@ function _elgg_admin_init() {
elgg_register_plugin_hook_handler('prepare', 'notification:remove_admin:user:user', '_elgg_admin_prepare_admin_notification_remove_admin');
elgg_register_plugin_hook_handler('prepare', 'notification:remove_admin:user:user', '_elgg_admin_prepare_user_notification_remove_admin');

// new users require admin validation
elgg_register_event_handler('login:before', 'user', '_elgg_admin_user_validation_login_attempt', 999); // allow others to throw exceptions earlier
elgg_register_event_handler('validate:after', 'user', '_elgg_admin_user_validation_notification');
elgg_register_plugin_hook_handler('cron', 'daily', '_elgg_admin_cron_pending_user_validation_notification');
elgg_register_plugin_hook_handler('cron', 'weekly', '_elgg_admin_cron_pending_user_validation_notification');
elgg_register_plugin_hook_handler('register', 'user', '_elgg_admin_check_admin_validation', 999); // allow others to also disable the user
elgg_register_plugin_hook_handler('response', 'action:register', '_elgg_admin_set_registration_forward_url', 999); // allow other to set forwar url first
elgg_register_plugin_hook_handler('usersettings:save', 'user', '_elgg_admin_save_notification_setting');

// Add notice about pending upgrades
elgg_register_event_handler('create', 'object', '_elgg_create_notice_of_pending_upgrade');
}
Expand Down Expand Up @@ -1085,6 +1095,226 @@ function _elgg_admin_upgrades_menu(\Elgg\Hook $hook) {
return $result;
}

/**
* Check if new users need to be validated by an administrator
*
* @param \Elgg\Hook $hook 'register', 'user'
*
* @return void
* @internal
* @since 3.2
*/
function _elgg_admin_check_admin_validation(\Elgg\Hook $hook) {

if (!(bool) elgg_get_config('require_admin_validation')) {
return;
}

$user = $hook->getUserParam();
if (!$user instanceof ElggUser) {
return;
}

elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($user) {

if ($user->isEnabled()) {
// disable the user until validation
$user->disable('admin_validation_required', false);
}

// set validation status
$user->setValidationStatus(false);

// store a flag in session so we can forward the user correctly
$session = elgg_get_session();
$session->set('admin_validation', true);

if (elgg_get_config('admin_validation_notification') === 'direct') {
_elgg_admin_notify_admins_pending_user_validation();
}
});
}

/**
* Prevent unvalidated users from logging in
*
* @param \Elgg\Event $event 'login:before', 'user'
*
* @return void
* @throws LoginException
* @internal
* @since 3.2
*/
function _elgg_admin_user_validation_login_attempt(\Elgg\Event $event) {

if (!(bool) elgg_get_config('require_admin_validation')) {
return;
}

$user = $event->getObject();
if (!$user instanceof ElggUser) {
return;
}

elgg_call(ELGG_SHOW_DISABLED_ENTITIES, function() use ($user) {
if ($user->isEnabled() && $user->isValidated() !== false) {
return;
}

throw new LoginException(elgg_echo('LoginException:AdminValidationPending'));
});
}

/**
* Send a notification to all admins that there are pending user validations
*
* @return void
* @internal
* @since 3.2
*/
function _elgg_admin_notify_admins_pending_user_validation() {

if (empty(elgg_get_config('admin_validation_notification'))) {
return;
}

$unvalidated_count = elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() {
return elgg_count_entities([
'type' => 'user',
'metadata_name_value_pairs' => [
'validated' => 0,
],
]);
});
if (empty($unvalidated_count)) {
// shouldn't be able to get here because this function is triggered when a user is marked as unvalidated
return;
}

$site = elgg_get_site_entity();
$admins = elgg_get_admins([
'limit' => false,
'batch' => true,
]);

$url = elgg_normalize_url('admin/users/unvalidated');

/* @var $admin ElggUser */
foreach ($admins as $admin) {
$user_setting = $admin->getPrivateSetting('admin_validation_notification');
if (isset($user_setting) && !(bool) $user_setting) {
continue;
}

$subject = elgg_echo('admin:notification:unvalidated_users:subject', [$site->getDisplayName()], $admin->getLanguage());
$body = elgg_echo('admin:notification:unvalidated_users:body', [
$admin->getDisplayName(),
$unvalidated_count,
$site->getDisplayName(),
$url,
], $admin->getLanguage());

$params = [
'action' => 'admin:unvalidated',
'object' => $admin,
];
notify_user($admin->guid, $site->guid, $subject, $body, $params, ['email']);
}
}

/**
* Save a setting related to admin approval of new users
*
* @param \Elgg\Hook $hook 'usersettings:save', 'user'
*
* @return void
* @internal
* @since 3.2
*/
function _elgg_admin_save_notification_setting(\Elgg\Hook $hook) {

$user = $hook->getUserParam();
if (!$user instanceof ElggUser || !$user->isAdmin()) {
return;
}

$request = $hook->getParam('request');
if (!$request instanceof \Elgg\Request) {
return;
}

$value = (bool) $request->getParam('admin_validation_notification', true);
$user->setPrivateSetting('admin_validation_notification', $value);
}

/**
* Set the correct forward url after user registration
*
* @param \Elgg\Hook $hook 'response', 'action:register'
*
* @return void
* @internal
* @since 3.2
*/
function _elgg_admin_set_registration_forward_url(\Elgg\Hook $hook) {

$response = $hook->getValue();
if (!$response instanceof ResponseBuilder) {
return;
}

$session = elgg_get_session();
if (!$session->get('admin_validation')) {
return;
}

// if other plugins already have set forwarding, don't do anything
if (!empty($response->getForwardURL()) && $response->getForwardURL() !== REFERER) {
return;
}

$response->setForwardURL(elgg_generate_url('account:validation:pending'));

return $response;
}

/**
* Notify the user that their account is approved
*
* @param \Elgg\Event $event 'validate:after', 'user'
*
* @return void
* @internal
* @since 3.2
*/
function _elgg_admin_user_validation_notification(\Elgg\Event $event) {

if (!(bool) elgg_get_config('require_admin_validation')) {
return;
}

$user = $event->getObject();
if (!$user instanceof ElggUser) {
return;
}

$site = elgg_get_site_entity();

$subject = elgg_echo('account:notification:validation:subject', [$site->getDisplayName()], $user->getLanguage());
$body = elgg_echo('account:notification:validation:body', [
$user->getDisplayName(),
$site->getDisplayName(),
$site->getURL(),
], $user->getLanguage());

$params = [
'action' => 'account:validated',
'object' => $user,
];

notify_user($user->guid, $site->guid, $subject, $body, $params, ['email']);
}

/**
* @see \Elgg\Application::loadCore Do not do work here. Just register for events.
*/
Expand Down
8 changes: 8 additions & 0 deletions engine/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@
\Elgg\Router\Middleware\SignedRequestGatekeeper::class,
],
],
'account:validation:pending' => [
'path' => '/validation_pending',
'resource' => 'account/validation_pending',
'walled' => false,
'middleware' => [
\Elgg\Router\Middleware\LoggedOutGatekeeper::class,
],
],
'ajax' => [
'path' => '/ajax/{segments}',
'handler' => '_elgg_ajax_page_handler',
Expand Down
1 change: 1 addition & 0 deletions engine/tests/classes/Elgg/Mocks/Database/ConfigTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class ConfigTable extends \Elgg\Database\ConfigTable {
'__site_secret__' => "z1234567890123456789012345678901",
'admin_registered' => 1,
'allow_registration' => 1,
'require_admin_validation' => 0,
'allow_user_default_access' => 1,
'default_access' => 2,
'default_limit' => 10,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Elgg\Hook;
use Elgg\Http\ErrorResponse;
use Elgg\Http\OkResponse;
use Elgg\Values;

/**
* @group ActionsService
Expand Down Expand Up @@ -297,4 +296,34 @@ public function testRegistrationSucceedsButExceptionThrownFromHook() {
$hook->assertNumberOfCalls(1);
$hook->unregister();
}
}

public function testRegisterWithAdminValidation() {

_elgg_config()->require_admin_validation = true;

// re-register admin validation hooks
_elgg_services()->hooks->registerHandler('register', 'user', '_elgg_admin_check_admin_validation', 999);

$username = $this->getRandomUsername();

$response = $this->executeAction('register', [
'username' => $username,
'password' => '1111111111111',
'password2' => '1111111111111',
'email' => $this->getRandomEmail(),
'name' => 'Test User',
]);

$this->assertInstanceOf(OkResponse::class, $response);

/* @var $user \ElggUser */
$user = elgg_call(ELGG_SHOW_DISABLED_ENTITIES, function () use ($username) {
return get_user_by_username($username);
});
$this->assertInstanceOf(\ElggUser::class, $user);
$this->assertFalse($user->isValidated());
$this->assertFalse($user->isEnabled());

$this->assertEmpty(elgg_get_logged_in_user_entity());
}
}
Loading

0 comments on commit 2882da6

Please sign in to comment.