Commit 224d6f2e authored by Robert Lyon's avatar Robert Lyon Committed by Gerrit Code Review
Browse files

Merge "Prevent new users from taking spammy actions"

parents d736111f 7b08f438
......@@ -93,6 +93,14 @@ $form = pieform(array(
'elements' => $elements,
));
function edit_comment_validate(Pieform $form, $values) {
require_once(get_config('libroot.php') . 'antispam.php');
$result = probation_validate_content($values['message']);
if ($result !== true) {
$form->set_error('message', get_string('newuserscantpostlinksorimages'));
}
}
function edit_comment_submit(Pieform $form, $values) {
global $viewid, $comment, $SESSION, $goto, $USER;
......
......@@ -971,8 +971,8 @@ function delete_comment_submit(Pieform $form, $values) {
}
function add_feedback_form_validate(Pieform $form, $values) {
require_once(get_config('libroot') . 'antispam.php');
if ($form->get_property('spam')) {
require_once(get_config('libroot') . 'antispam.php');
$spamtrap = new_spam_trap(array(
array(
'type' => 'body',
......@@ -992,6 +992,10 @@ function add_feedback_form_validate(Pieform $form, $values) {
if (empty($values['attachments']) && empty($values['message'])) {
$form->set_error('message', get_string('messageempty', 'artefact.comment'));
}
$result = probation_validate_content($values['message']);
if ($result !== true) {
$form->set_error('message', get_string('newuserscantpostlinksorimages'));
}
}
function add_feedback_form_submit(Pieform $form, $values) {
......
......@@ -81,6 +81,7 @@ class User {
'showhomeinfo' => 1,
'unread' => 0,
'urlid' => null,
'probation' => 0,
);
$this->attributes = array();
......
......@@ -129,7 +129,7 @@ class PluginBlocktypeWall extends SystemBlocktype {
'jsform' => true,
'template' => 'wallpost.php',
'templatedir' => pieform_template_dir('wallpost.php', 'blocktype/wall'),
// 'validatecallback' => array('PluginBlocktypeWall', 'wallpost_validate'),
'validatecallback' => array('PluginBlocktypeWall', 'wallpost_validate'),
'successcallback' => array('PluginBlocktypeWall', 'wallpost_submit'),
'jssuccesscallback' => 'wallpost_success',
'elements' => array(
......@@ -192,6 +192,14 @@ EOF;
return "<script>$js</script>";
}
public static function wallpost_validate(Pieform $form, $values) {
require_once(get_config('libroot') . 'antispam.php');
$result = probation_validate_content($values['text']);
if ($result !== true) {
$form->set_error('text', get_string('newuserscantpostlinksorimages'));
}
}
public static function wallpost_submit(Pieform $form, $values) {
global $USER;
$record = (object)array(
......
......@@ -7,7 +7,7 @@ echo $form_tag;
echo '<div id="wall"><div class="description">' . $elements['postsizelimit']['html'] . ' ' . $elements['text']['description'] . '</div>';
echo '<div>' . $elements['text']['labelhtml'] . $elements['text']['html'] .'</div>';
if (isset($elements['text']['error'])) {
echo '<div>' . $elements['text']['error'] . '</div>';
echo '<div class="errmsg">' . $elements['text']['error'] . '</div>';
}
echo '<div class="makeprivate">' . $elements['private']['labelhtml'] . ' ' . $elements['private']['html'] . '</div>';
echo '<div>' . $elements['submit']['html'] . '</div></div>';
......
......@@ -14,6 +14,7 @@ define('MENUITEM', 'groups/groupsiown');
require(dirname(dirname(__FILE__)) . '/init.php');
require_once('pieforms/pieform.php');
require_once('group.php');
require_once(get_config('libroot') . 'antispam.php');
if ($id = param_integer('id', null)) {
define('TITLE', get_string('editgroup', 'group'));
......@@ -234,6 +235,7 @@ else {
}
$publicallowed = get_config('createpublicgroups') == 'all' || (get_config('createpublicgroups') == 'admins' && $USER->get('admin'));
$publicallowed = $publicallowed && !is_probationary_user();
if (!$id && !param_exists('pieform_editgroup')) {
// If a 'public=0' parameter is passed on the first page load, hide the
......@@ -440,7 +442,7 @@ function editgroup_cancel_submit() {
}
function editgroup_submit(Pieform $form, $values) {
global $USER, $SESSION, $group_data;
global $USER, $SESSION, $group_data, $publicallowed;
$values['public'] = (isset($values['public'])) ? $values['public'] : 0;
$values['usersautoadded'] = (isset($values['usersautoadded'])) ? $values['usersautoadded'] : 0;
......@@ -454,7 +456,7 @@ function editgroup_submit(Pieform $form, $values) {
'controlled' => intval($values['controlled']),
'request' => intval($values['request']),
'usersautoadded' => intval($values['usersautoadded']),
'public' => intval($values['public']),
'public' => ($publicallowed ? intval($values['public']) : 0),
'viewnotify' => intval($values['viewnotify']),
'submittableto' => intval($values['submittableto']),
'editroles' => $values['editroles'],
......
......@@ -176,6 +176,10 @@ function editpost_validate(Pieform $form, $values) {
if ($baddomain = get_first_blacklisted_domain($values['body'])) {
$form->set_error('body', get_string('blacklisteddomaininurl', 'mahara', $baddomain));
}
$result = probation_validate_content($values['body']);
if ($result !== true) {
$form->set_error('body', get_string('newuserscantpostlinksorimages'));
}
}
function editpost_submit(Pieform $form, $values) {
......@@ -244,6 +248,12 @@ function addpost_submit(Pieform $form, $values) {
PluginInteractionForum::interaction_forum_new_post(array($postid));
}
$SESSION->add_ok_msg(get_string('addpostsuccess', 'interaction.forum'));
if (is_using_probation() && $post->parent) {
$parentposter = get_field('interaction_forum_post', 'poster', 'id', $post->parent);
vouch_for_probationary_user($parentposter);
}
redirect(get_config('wwwroot') . 'interaction/forum/topic.php?id=' . $values['topic'] . '&post=' . $postid);
}
......
......@@ -175,12 +175,20 @@ function addtopic_validate(Pieform $form, $values) {
if ($baddomain = get_first_blacklisted_domain($values['body'])) {
$form->set_error('body', get_string('blacklisteddomaininurl', 'mahara', $baddomain));
}
$result = probation_validate_content($values['body']);
if ($result !== true) {
$form->set_error('body', get_string('newuserscantpostlinksorimages'));
}
}
function edittopic_validate(Pieform $form, $values) {
if ($baddomain = get_first_blacklisted_domain($values['body'])) {
$form->set_error('body', get_string('blacklisteddomaininurl', 'mahara', $baddomain));
}
$result = probation_validate_content($values['body']);
if ($result !== true) {
$form->set_error('body', get_string('newuserscantpostlinksorimages'));
}
}
function addtopic_submit(Pieform $form, $values) {
......
......@@ -644,6 +644,7 @@ $string['spamtrap'] = 'Spam trap';
$string['formerror'] = 'There was an error processing your submission. Please try again.';
$string['formerroremail'] = 'Contact us at %s if you continue to have problems.';
$string['blacklisteddomaininurl'] = 'A URL in this field contains the blacklisted domain %s.';
$string['newuserscantpostlinksorimages'] = 'Sorry, newly registered users aren\'t allowed to post links. Please reword your post to remove any links or URLs and try again.';
$string['notinstallable'] = 'Not installable';
$string['installedplugins'] = 'Installed plugins';
......
......@@ -104,7 +104,7 @@ $string['generatesecreturl'] = 'Generate a new secret URL for %s';
$string['secreturls'] = 'Secret URLs';
$string['publicaccessnotallowed'] = "Your institution or site administrator has disabled public pages and secret URLs. Any secret URLs you see listed here are currently inactive.";
$string['publicaccessnotallowedforprobation'] = "Sorry, newly registered users aren't allowed to create secret URLs.";
// view user
$string['inviteusertojoingroup'] = 'Invite this user to join a group';
$string['addusertogroup'] = 'Add this user to a group';
......
......@@ -56,3 +56,107 @@ if (!function_exists('checkdnsrr')) {
return false;
}
}
/**
* Check whether a user is on probation.
* @param int $userid
* @return boolean TRUE if the user is on probation, FALSE if the user is not on probation
*/
function is_probationary_user($userid = null) {
global $USER;
// Check whether a new user threshold is in place or not.
if (!is_using_probation()) {
return false;
}
// Get the user's information
if ($userid == null) {
$user = $USER;
}
else {
$user = new User();
$user->find_by_id($userid);
}
// Admins and staff get a free pass
if ($user->get('admin') || $user->get('staff') || $user->is_institutional_admin() || $user->is_institutional_staff()) {
return false;
}
// We actually store new user points in reverse. When your account is created, you get $newuserthreshold points, and
// we decrease those when you do something good, and when it hits 0 you're no longer a new user.
$userspoints = get_field('usr', 'probation', 'id', $user->get('id'));
if ($userspoints > 0) {
return true;
}
else {
return false;
}
}
/**
* Activity that "vouches" for a new user to indicate that they're a real person, should call this
*
* @param int $vouchforthisuserid The userid of the person being vouched for
* @param int $vouchinguserid The userid of the person doing the vouching
* @return boolean TRUE if we can vouch for the person, FALSE if not
*/
function vouch_for_probationary_user($probationaryuserid, $vouchinguserid = null, $points = 1) {
global $USER;
// Check whether we're even using this system.
if (!is_using_probation()) {
return true;
}
if ($vouchinguserid == null) {
$vouchinguserid = $USER->get('id');
}
// A new user can't vouch for another new user
if (is_probationary_user($vouchinguserid)) {
return false;
}
$voucheepoints = get_field('usr', 'probation', 'id', $probationaryuserid);
if ($voucheepoints > 0) {
set_field('usr', 'probation', max(0, $voucheepoints - $points), 'id', $probationaryuserid);
}
return true;
}
/**
* Indicates whether we're using a probation threshold
* @return boolean
*/
function is_using_probation() {
return (boolean) (get_config('probationenabled') && get_config('probationstartingpoints'));
}
/**
* Check for external links and images being posted by a probationary user
* @param string $text
* @return BOOLEAN true if the text is okay, false if not
*/
function probation_validate_content($text) {
if (!is_using_probation()) {
return true;
}
if (!has_external_links_or_images($text)) {
return true;
}
if (is_probationary_user()) {
return false;
}
return true;
}
function has_external_links_or_images($text) {
// Check to see whether the post contains any content forbidden to new users
// (We do this first, in order to avoid any unnecessary hits to the DB
return (boolean) preg_match('#(://)|(<a\b)#i', $text);
}
\ No newline at end of file
......@@ -583,4 +583,18 @@ $cfg->publicsearchallowed = false;
* their accounts. Leave on the default "false" to only allow users to delete their accounts if they
* belong to an institution that allows self-registration.
*/
//$cfg->alwaysallowselfdelete = true;
\ No newline at end of file
//$cfg->alwaysallowselfdelete = true;
/**
* @global boolean $cfg->probationenabled Determines whether or not to use the new-user probation system.
* If enabled, this will prevent newly self-registered users from taking certain actions that would be
* useful for creating spam content. Users will leave probation once non-probationary users take actions
* that indicate they trust the probationary user.
*/
$cfg->probationenabled = false;
/**
* @global integer $cfg->probationstartingpoints If new user probation is enabled, this setting determines how
* many probation points new users will start with.
*/
$cfg->probationstartingpoints = 2;
......@@ -162,6 +162,7 @@
<FIELD NAME="logintries" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" />
<FIELD NAME="unread" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" />
<FIELD NAME="urlid" TYPE="char" LENGTH="30" NOTNULL="false" />
<FIELD NAME="probation" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" />
<!-- If adding a datetime column, you should also update the relevant
functions in htdocs/auth/user.php and htdocs/lib/user.php to convert the
value to a timestamp when a record is retrieved -->
......
......@@ -3181,5 +3181,14 @@ function xmldb_core_upgrade($oldversion=0) {
add_field($table, $field);
}
if ($oldversion < 2014032500) {
$table = new XMLDBTable('usr');
$field = new XMLDBField('probation');
$field->setAttributes(XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, null, null, 0);
if (!field_exists($table, $field)) {
add_field($table, $field);
}
}
return $status;
}
......@@ -28,8 +28,12 @@ function pieform_element_viewacl(Pieform $form, $element) {
$value = $form->get_value($element);
// Look for the presets and split them into two groups
require_once(get_config('libroot') . 'antispam.php');
$public = false;
if (get_config('allowpublicviews') && $USER->institution_allows_public_views()) {
if (is_probationary_user()) {
$public = false;
}
else if (get_config('allowpublicviews') && $USER->institution_allows_public_views()) {
$public = true;
}
else if (get_config('allowpublicprofiles') && $element['viewtype'] == 'profile') {
......
......@@ -2017,13 +2017,13 @@ function can_view_view($view, $user_id=null) {
$publicviews = get_config('allowpublicviews');
$publicprofiles = get_config('allowpublicprofiles');
// If the user is logged out and the publicviews & publicprofiles sitewide configs are false,
// we can deny access without having to hit the database at all
if (!$user_id && !$publicviews && !$publicprofiles) {
return false;
}
if (!class_exists('View')) {
require_once(get_config('libroot') . 'view.php');
}
require_once(get_config('libroot') . 'view.php');
if ($view instanceof View) {
$view_id = $view->get('id');
}
......@@ -2031,14 +2031,35 @@ function can_view_view($view, $user_id=null) {
$view = new View($view_id = $view);
}
// group views and logged in users are not affected by
// the institution level config for public views
if (empty($user_id) && $ownerobj = $view->get_owner_object()) {
$owner = new User();
$owner->find_by_id($ownerobj->id);
if (!$owner->institution_allows_public_views()) {
// If the page belongs to an individual, check for individual-specific overrides
if ($view->get('owner')) {
$ownerobj = $view->get_owner_object();
// Suspended user
if ($ownerobj->suspendedctime) {
return false;
}
// Probationary user (no public pages or profiles)
// (setting these here instead of doing a return-false, so that we can do checks for
// logged-in users later)
require_once(get_config('libroot') . 'antispam.php');
$onprobation = is_probationary_user($ownerobj->id);
$publicviews = $publicviews && !$onprobation;
$publicprofiles = $publicprofiles && !$onprobation;
// Member of an institution that prohibits public pages
// (group views and logged in users are not affected by
// the institution level config for public views)
$owner = new User();
$owner->find_by_id($ownerobj->id);
$publicviews = $publicviews && $owner->institution_allows_public_views();
}
// Now that we've examined the page owner, check again for whether it can be viewed by a logged-out user
if (!$user_id && !$publicviews && !$publicprofiles) {
return false;
}
if ($user_id && $user->can_edit_view($view)) {
......
......@@ -15,7 +15,7 @@ $config = new stdClass();
// See https://wiki.mahara.org/index.php/Developer_Area/Version_Numbering_Policy
// For upgrades on stable branches, increment the version by one. On master, use the date.
$config->version = 2014032400;
$config->version = 2014032500;
$config->release = '1.9.0dev';
$config->minupgradefrom = 2009022600;
$config->minupgraderelease = '1.1.0 (release tag 1.1.0_RELEASE)';
......
......@@ -4997,11 +4997,17 @@ class View {
return $data;
}
if (!function_exists('is_probationary_user')) {
require_once(get_config('libroot') . 'antispam.php');
}
foreach ($accessgroups as $access) {
// remove 'Public' from the list if the owner isn't allowed to have them
if ($access->accesstype == 'public'
&& (get_config('allowpublicviews') != 1
|| (isset($ownerobj) && !$ownerobj->institution_allows_public_views()))
&& (
get_config('allowpublicviews') != 1
|| (isset($ownerobj) && !$ownerobj->institution_allows_public_views())
|| (isset($ownerobj) && is_probationary_user($ownerobj->id))
)
) {
continue;
}
......
......@@ -115,6 +115,15 @@ if (isset($key)) {
$user->username = get_new_username($user->firstname . $user->lastname);
$user->passwordchange = 1;
// Points that indicate the user is a "new user" who should be restricted from spammy activities.
// We count these down when they do good things; when they have 0 they're no longer a "new user"
if (is_using_probation()) {
$user->probation = get_config('probationstartingpoints');
}
else {
$user->probation = 0;
}
if ($registration->institution != 'mahara') {
if (count_records_select('institution', "name != 'mahara'") == 1 || $registration->pending == 2) {
if (get_config_plugin('artefact', 'file', 'institutionaloverride')) {
......
{include file="header.tpl"}
{if !$allownew}
<div class="message info">{str tag=publicaccessnotallowed section=view}</div>
<div class="message info">{if $onprobation}{str tag=publicaccessnotallowedforprobation section=view}{else}{str tag=publicaccessnotallowed section=view}{/if}</div>
{/if}
{if $editurls}
......
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