Anzeige
Tutorialbeschreibung

Joomla! - erneutes Voten vermeiden

Joomla! - erneutes Voten vermeiden

Wenn man auf seiner Joomla!-Homepage die Umfragekomponente com_poll verwendet, kann es vorkommen, dass manche Besucher ihre Cookies löschen und erneut voten. Das kann die Ergebnisse stark beeinflussen; deshalb zeige ich hier eine Möglichkeit, wie man die Komponente absichert.


Vor einigen Tagen wurde ich mit dem Problem konfrontiert, dass manche Besucher ihre Cookies löschen und erneut voten. Deshalb habe ich com_poll angepasst und nun ist das Problem beseitigt. Gerne möchte ich euch zeigen, wie das geht.

Joomla! speichert nicht jede einzelne Stimme; es wird lediglich die Anzahl der Stimmen gespeichert. Meine Überlegung war also, eine neue Tabelle zu erstellen, in der die Benutzer gespeichert werden, die bereits gevotet haben. Natürlich muss das Voten dazu nur für registrierte Benutzer zugänglich sein.


Neue Tabelle: jos_polls_control mit den Feldern id, poll_data_id, user_id, ip, date, tries.

SQL-Befehl für phpmyadmin:

CREATE TABLE IF NOT EXISTS `jos_polls_control` (
  `id` smallint(5) NOT NULL AUTO_INCREMENT,
  `poll_data_id` smallint(5) NOT NULL,
  `user_id` smallint(5) NOT NULL,
  `ip` varchar(15) NOT NULL,
  `date` int(10) NOT NULL,
  `tries` smallint(5) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;

Und eine weitere Tabelle jos_polls_attempt mit den Feldern id, poll_data_id, user_id, ip, date, um erneute Abstimmversuche zu speichern.

CREATE TABLE IF NOT EXISTS `jos_polls_attempt` (
  `id` smallint(5) NOT NULL AUTO_INCREMENT,
  `poll_data_id` smallint(5) NOT NULL,
  `user_id` smallint(5) NOT NULL,
  `ip` varchar(15) NOT NULL,
  `date` int(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=13 ;
Jetzt müssen noch zwei Dateien angepasst werden:

components/com_poll/controller.php

<?php
/**
* @version		$Id: controller.php 10869 2008-08-30 07:24:03Z willebil $
* @package		Joomla
* @subpackage	Polls
* @copyright	Copyright (C) 2005 - 2008 Open Source Matters. All rights reserved.
* @license		GNU/GPL, see LICENSE.php
* Joomla! is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/

// no direct access
defined( '_JEXEC' ) or die( 'Restricted access' );

jimport('joomla.application.component.controller');

/**
 * Static class to hold controller functions for the Poll component
 *
 * @static
 * @package		Joomla
 * @subpackage	Poll
 * @since		1.5
 */
 
class PollController extends JController
{
	/**
	 * Method to show the search view
	 *
	 * @access	public
	 * @since	1.5
	 */
	function display()
	{
		JRequest::setVar('view','poll'); // force it to be the polls view
		parent::display();
	}

	/**
 	 * Add a vote to an option
 	 */
	function vote()
	{
		global $mainframe;

		// Check for request forgeries
		JRequest::checkToken() or jexit( 'Invalid Token' );

		$db			=& JFactory::getDBO();
		$poll_id	= JRequest::getVar( 'id', 0, '', 'int' );
		$option_id	= JRequest::getVar( 'voteid', 0, 'post', 'int' );
        $user_ip = $_SERVER['REMOTE_ADDR'];
         
        $user =& JFactory::getUser();
        $user_id = $user->id;
        

		$poll =& JTable::getInstance('poll','Table');
		if (!$poll->load( $poll_id ) || $poll->published != 1) {
			JError::raiseWarning( 404, JText::_('ALERTNOTAUTH') );
			return;
		}

		$cookieName	= JUtility::getHash( $mainframe->getName() . 'poll' . $poll_id );
		// ToDo - may be adding those information to the session?
		$voted = JRequest::getVar( $cookieName, '0', 'COOKIE', 'INT');

		if ($voted || !$option_id )
		{
			if($voted) {
				$msg = JText::_('You already voted for this poll today!');
			}

			if(!$option_id){
				$msg = JText::_('WARNSELECT');
			}
		}
		else
		{
			setcookie( $cookieName, '1', time() + $poll->lag );

			require_once(JPATH_COMPONENT.DS.'models'.DS.'poll.php');
			$model = new PollModelPoll();
            
            $query = 'SELECT COUNT(*) as count FROM'
                . ' jos_polls_control join jos_poll_data on'
                . ' jos_polls_control.poll_data_id = jos_poll_data.id WHERE'
                . ' pollid = ' . (int) $poll_id
                . ' AND'
                . " user_id = '" . $user_id . "'"
            ;                
                
    		$db->setQuery($query);
    		$result = $db->loadObject();

            if($result->count>0){                
                $model->voteattempt( $poll_id, $option_id, $user_ip, $user_id);
                $msg = JText::_( 'You already voted for this poll today!' );
            } else {
                $model->vote( $poll_id, $option_id, $user_ip, $user_id);
                $msg = JText::_( 'Thanks for your vote!' );
            }			
		}
        
		// set Itemid id for links
		$menu = &JSite::getMenu();
		$items	= $menu->getItems('link', 'index.php?option=com_poll&view=poll');

		$itemid = isset($items[0]) ? '&Itemid='.$items[0]->id : '';

		$this->setRedirect( JRoute::_('index.php?option=com_poll&id='. $poll_id.':'.$poll->alias.$itemid, false), $msg );
	}
}
?>

Beschreibung dazu:

Diese Befehle speichern die IP-Adresse und die UserID in die entsprechenden Variablen:

        $user_ip = $_SERVER['REMOTE_ADDR'];
       
        $user =& JFactory::getUser();
        $user_id = $user->id;


Hier findet eine Abfrage statt, ob der User schon gevotet hat und dann wird weiter entschieden, ob die Stimme nun gezählt oder in der Liste mit den doppelten Stimmen gespeichert wird. Hier werden auch die neuen Parameter (ip und id) den Funktionen übergeben.

            $query = 'SELECT COUNT(*) as count FROM'
                . ' jos_polls_control join jos_poll_data on'
                . ' jos_polls_control.poll_data_id = jos_poll_data.id WHERE'
                . ' pollid = ' . (int) $poll_id
                . ' AND'
                . " user_id = '" . $user_id . "'"
            ;                
                
    		$db->setQuery($query);
    		$result = $db->loadObject();

            if($result->count>0){                
                $model->voteattempt( $poll_id, $option_id, $user_ip, $user_id);
                $msg = JText::_( 'You already voted for this poll today!' );
            } else {
                $model->vote( $poll_id, $option_id, $user_ip, $user_id);
                $msg = JText::_( 'Thanks for your vote!' );
            }

Jetzt kommt noch die Datei components/com_poll/models/poll.php; hier sind die Funktionen deklariert.

Einmal komplett:

<?php
/**
* @version		$Id: poll.php 10752 2008-08-23 01:53:31Z eddieajau $
* @package		Joomla
* @subpackage	Polls
* @copyright	Copyright (C) 2005 - 2008 Open Source Matters. All rights reserved.
* @license		GNU/GPL, see LICENSE.php
* Joomla! is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/

// Check to ensure this file is included in Joomla!
defined('_JEXEC') or die( 'Restricted access' );

jimport( 'joomla.application.component.model' );

/**
* @package		Joomla
* @subpackage	Polls
*/
class PollModelPoll extends JModel
{
	/**
	 * Add vote
	 * @param int The id of the poll
	 * @param int The id of the option selected
	 */
	function vote( $poll_id, $option_id, $user_ip, $user_id )
	{
		$db = $this->getDBO();
		$poll_id	= (int) $poll_id;
		$option_id	= (int) $option_id;
        $user_ip = $user_ip;
        $user_id = (int) $user_id;
              
        $query = 'INSERT INTO #__polls_control'
			. ' SET poll_data_id = ' . (int) $option_id
			. ", user_id = '" . $user_id . "'"
			. ", ip = '" . $user_ip . "'"
			. ', date = ' . time()
			. ', tries = 1'
		;
        
		$db->setQuery( $query );
		$db->query();

		$query = 'UPDATE #__poll_data'
			. ' SET hits = hits + 1'
			. ' WHERE pollid = ' . (int) $poll_id
			. ' AND id = ' . (int) $option_id
			;
		$db->setQuery( $query );
		$db->query();

		$query = 'UPDATE #__polls'
			. ' SET voters = voters + 1'
			. ' WHERE id = ' . (int) $poll_id
			;
		$db->setQuery( $query );
		$db->query();

		$date =& JFactory::getDate();

		$query = 'INSERT INTO #__poll_date'
			. ' SET date = ' . $db->Quote($date->toMySQL())
			. ', vote_id = ' . (int) $option_id
			. ', poll_id = ' . (int) $poll_id
		;
		$db->setQuery( $query );
		$db->query();
        
	}
	function voteattempt( $poll_id, $option_id, $user_ip, $user_id )
	{
		$db = $this->getDBO();
		$poll_id	= (int) $poll_id;
		$option_id	= (int) $option_id;
        $user_ip = $user_ip;
        $user_id = (int) $user_id;

        $query = 'UPDATE #__polls_control join #__poll_data on'
            . ' #__polls_control.poll_data_id = #__poll_data.id'
			. ' SET tries = tries + 1'
            . ' WHERE'
            . ' pollid = ' . (int) $poll_id
            . ' AND'
            . " user_id = '" . $user_id . "'"
		;
            
		$db->setQuery( $query );
		$db->query();            

        $query = 'INSERT INTO #__polls_attempt'
			. ' SET poll_data_id = ' . (int) $option_id
            . ", user_id = '" . $user_id . "'"
			. ", ip = '" . $user_ip . "'"
			. ', date = ' . time()
		;
            
		$db->setQuery( $query );
		$db->query();            

        
	}
}

Die "alte" Funktion vote() habe ich hier erweitert, damit die User-ID beim Voten mit der IP und der Zeit in der neuen Tabelle gespeichert wird. Und zusätzlich ist die neue Funktion voteattempt() hinzugekommen; diese fängt die doppelten Stimmen ab und speichert diese in der zweiten neuen Tabelle. Das Feld "tries" ist nicht wesentlich wichtig, nur für die Bequemlichkeit, sofort solche Votes in der Tabelle zu erkennen. Beim jedem neuen doppelten Vote erhöht sich die Zahl jeweils um eins.


Ich hoffe mal, das ist soweit klar; Livedemo gibt's diesmal nicht, da eigentlich unnötig.

Kommentare
Achtung: Du kannst den Inhalt erst nach dem Login kommentieren.
Alternative Portrait
-versteckt-(Autor hat Seite verlassen)
  • 11.10.2010 - 22:29

super danke dass kann ich gut ebrauchen

Portrait von topmanager
  • 04.10.2010 - 15:02

schön und übersichtlich erklärt,
so verstehe ich das sogar

Portrait von romox
  • 29.05.2010 - 17:11

Ok, jetzt die versprochene Anpassung, um auch Leute auszuschließen, die sich von der selben IP mehrmals registrieren und dann abstimmen.

Wir ersetzen die Zeile 94 in controller.php

. " user_id = '" . $user_id . "'"

druch folgende:

. " (user_id = '" . $user_id . "' OR ip = '" . $user_ip . "')";

Vorher wurden User gezählt, die schon (in dieser Kategorie) abgestimmt haben. Jetzt werden User OR (soviel wie und/oder auf gut Deutsch) andere User mit der selben IP gezählt und falls Anzahl größer als 0 ist, wird die Stimme nicht gezählt. Klar, wir schließen damit Personen im selben Netzwerk und Personen, die später diese IP erhalten und nicht gestimmt haben aus. Leider musste ich dies aber in Kauf nehmen, da jemand versucht hat für eine bestimmte Option ganz oft zu stimmen und sich dafür auch jedes mal neu registriert hat. Wenn man jetzt noch die Verbindung trennt und eine neue IP vom Provider erhält, geht es trotzdem.

Weitere Überlegungen wären per JavaScript Systemparameter (Auflösung, Browser, Betriebssystem usw), Provider anhand der IP zu vergleichen und so versuchen den Angreifer zu bemerken. Oder auch Aktivierungscode per SMS zu verschicken. Das war allerdings aber zu viel Aufwand in diesem Fall.

Portrait von romox
  • 29.05.2010 - 13:14

Die Erfahrung hat gezeigt, dass das noch nicht reicht. Leider musste ich zusätzlich IP Abfrage einbauen. Ist zwar auch keine 100% Sicherheit, aber dennoch besser.

Den Code werde ich heute noch hier in den Kommentaren posten

Portrait von metallica1990
Portrait von fittererfamily
  • 29.05.2010 - 00:22

hab ich zwar nicht angeschaut aber hat mit trotzdem 3 punkte abgezogen, vielleicht versehentlich draufgekommen.
ich kann mit dem thema leider nichts anfangen.

Portrait von romox
  • 12.10.2010 - 11:27

Zu kompliziert oder interessiert das Thema einfach nicht? :-)

x
×
×