Skip to content

Commit

Permalink
Improve security
Browse files Browse the repository at this point in the history
Now add captcha id to checkbox prefix (allow many captcha usage at the same time).
Rework all the verify and display process
Add new Directive class to manage user directives
Rework collection management
  • Loading branch information
aetiom committed Feb 5, 2019
1 parent 4ec12b5 commit 84d56b7
Show file tree
Hide file tree
Showing 4 changed files with 281 additions and 81 deletions.
119 changes: 63 additions & 56 deletions src/Captcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,24 @@ class Captcha {
protected $options;

/**
* @var Collection $collection : captcha collection
* @var \VoightKampff\Collection $collection : captcha collection
*/
protected $collection = null;

/**
* @var \aetiom\PhpExt\Session $session : captcha session
* @var \VoightKampff\Security $security : captcha security
*/
protected $session = null;
protected $security = null;

/**
* @var \aetiom\PhpExt\MultiLang\Collection $directiveCol : directive collection
* @var \aetiom\PhpExt\MultiLang\Collection $errorCol : error collection
*/
protected $directiveCol = null;
protected $errorCol = null;

/**
* @var \aetiom\PhpExt\MultiLang\Collection $errorCol : error collection
* @var \VoightKampff\Directive $directive : directive container
*/
protected $errorCol = null;
protected $directive = null;

/**
* @var \aetiom\PhpExt\MultiLang\Container $error : error container
Expand Down Expand Up @@ -76,7 +76,11 @@ static public function obtainPostedImages($count, $prefix)
*/
public function getImages()
{
return $this->collection->getImages();
if ($this->collection !== null) {
return $this->collection->getImages();
}

return array();
}

/**
Expand All @@ -86,9 +90,13 @@ public function getImages()
* @return string : selected language directive message if it exist
* default language directive otherwise
*/
public function getDirective($lang = '')
public function getDirective($lang = null)
{
return $this->formatDirective($lang);
if ($this->collection !== null) {
return $this->directive->getMessage($lang);
}

return '';
}

/**
Expand Down Expand Up @@ -120,6 +128,7 @@ public function getOptions()
public function __construct($id, $param = array())
{
$this->options = new Options($param);
$this->options->cbPrefix = $id.'-'.$this->options->cbPrefix;

$this->errorCol = new \aetiom\PhpExt\MultiLang\Collection(
$this->options->errorCollection,
Expand All @@ -128,12 +137,7 @@ public function __construct($id, $param = array())
$this->collection = new Collection($id, $this->options);
$this->security = new Security($this->options);

if ($this->security->getTimeoutStatus()) {
$this->error = $this->errorCol->createContainer('timeout',
array('%TIME%' => $this->security->getTimeoutRemaining()));

$this->collection->clear();
}
$this->directive = new Directive($this->options, $this->collection);
}


Expand All @@ -146,15 +150,12 @@ public function __construct($id, $param = array())
*/
public function verify(array $userAnswers = null)
{
$answerList = $this->collection->getAnswers();
if ($userAnswers === null) {
$userAnswers = self::obtainPostedImages(
$this->options->imageCount, $this->options->cbPrefix);
}

if (!$this->security->isSessionActive()) {
$this->error = $this->errorCol->createContainer('inactive');
$this->collection->clear();
if ($this->checkInactivity()) {
return false;
}

Expand All @@ -163,16 +164,14 @@ public function verify(array $userAnswers = null)
return false;
}

if ($this->checkAnswers($userAnswers, $answerList)) {
$this->security->addAttempt();

if ($this->checkAnswers($userAnswers)) {
$this->collection->clear();
$this->security->clear();
return true;
}

if (!$this->security->addAttempt()) {
$this->collection->clear();
}

$this->error = $this->errorCol->createContainer('wrongAnswers');
return false;
}
Expand Down Expand Up @@ -205,13 +204,13 @@ public function display($lang = null)
/**
* Check user answers
*
* @param array $userAnswers : user answers
* @param array $expectedAnswers : system expected answers
*
* @param array $userAnswers : user answers
* @return boolean true if anwsers are those expected, false otherwise
*/
private function checkAnswers($userAnswers, $expectedAnswers)
private function checkAnswers($userAnswers)
{
$expectedAnswers = $this->collection->getAnswers();

// return false if user does not send count of expected answers
if (count($userAnswers) !== $this->options->requestCount) {
return false;
Expand All @@ -235,37 +234,45 @@ private function checkAnswers($userAnswers, $expectedAnswers)
}

/**
* Format directive
*
* @param string $lang : selected language
* @return string formated directive
* Check timeout status
* @return boolean true if user has been timeouted, false otherwise
*/
private function formatDirective($lang)
private function checkTimeout()
{
$start = $this->directiveCol->createContainer('start');
$link1 = $this->directiveCol->createContainer('linkSimple');
$link2 = $this->directiveCol->createContainer('linkMulti');
$end = $this->directiveCol->createContainer('end');
$kwIn = $this->directiveCol->createContainer('keywordIn');
$kwOut = $this->directiveCol->createContainer('keywordOut');

$dirStr = '';
foreach ($this->collection->getKeyWords() as $key => $q) {
if (!empty($dirStr)) {
if ($this->options['requestCount'] > 2
&& $key < $this->options['requestCount'] - 1) {
$dirStr .= $link2->getMessage($lang);
} else {
$dirStr .= $link1->getMessage($lang);
}
}
if ($this->security->getTimeoutRemaining() > 0) {
$this->error = $this->errorCol->createContainer('timeout',
array('%TIME%' => $this->security->getTimeoutRemaining()));


$dirStr .= $kwIn->getMessage($lang).$q[$lang].$kwOut->getMessage($lang);

$this->collection->clear();
return true;
}

if ($this->security->getTimeoutRemaining() < 0) {
$this->security->resetTimout();
$this->collection->reset();
}

return $start->getMessage($lang).$dirStr.$end->getMessage($lang);
return false;
}

/**
* Check inactivity status
*
* @param boolean $throwError : set false if error doesn't have to be throw
* @return boolean true if session is active, false otherwise
*/
private function checkInactivity(bool $throwError = true)
{
if ($this->security->isSessionActive()) {
return false;
}

if ($throwError) {
$this->error = $this->errorCol->createContainer('inactive');
}

$this->security->resetInactivity();
$this->collection->reset();
return true;
}
}
76 changes: 52 additions & 24 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,34 @@ public function getKeyWords()
public function __construct(string $id, Options $options)
{
$this->id = $id;
$this->session = new \aetiom\PhpExt\Session('styx-captcha');
$this->options = $options;

$this->session = new \aetiom\PhpExt\Session('voight-kampff');
$currentCol = $this->session->select($this->id)->fetch();

$this->setCollection($param);
if (!empty($currentCol)) {
$this->images = $currentCol['images'];
$this->keyWords = $currentCol['keyWords'];
$this->answers = $currentCol['answers'];
} else {
$this->setNewCollection();
}

shuffle($this->images);
shuffle($this->keyWords);

$this->session->select($this->id)
->update(array ('images' => $this->images,
'keyWords' => $this->keyWords,
'answers' => $this->answers));
$this->updateSession();
}



/**
* Reset collection by creating a new one
*/
public function reset()
{
$this->setNewCollection();
$this->updateSession();
}


Expand All @@ -111,9 +126,31 @@ public function __construct(string $id, Options $options)
*/
public function clear()
{
$this->images = array();
$this->answers = array();
$this->keyWords = array();

$this->updateSession();
}


public function delete()
{
$this->images = array();
$this->answers = array();
$this->keyWords = array();

$this->session->delete($this->id);
}

private function updateSession()
{
$this->session->select($this->id)
->update(array ('images' => $this->images,
'keyWords' => $this->keyWords,
'answers' => $this->answers));
}



/**
Expand Down Expand Up @@ -193,25 +230,16 @@ protected function selectKeyWords() {
* @param array $param : collection parameters containing 'imageCount',
* 'defaultLang' and 'requestCount' keys
*/
private function setCollection($param)
private function setNewCollection()
{
$currentCol = $this->session->select($this->id)->fetch();
if (!empty($currentCol)) {
$this->images = $currentCol['images'];
$this->keyWords = $currentCol['keyWords'];
$this->answers = $currentCol['answers'];
}

else {
$initPool = $this->initiatePool($param['pool']);

$this->selectImages($initPool, $param['imageCount']);
$this->selectKeyWords($param['defaultLang'], $param['requestCount']);

// remove 'lang' data from images selection for security reasons
for ($i=0; $i<$param['imageCount']; $i++) {
unset($this->images[$i]['lang']);
}
$this->initiatePool();

$this->selectImages();
$this->selectKeyWords();

// remove 'lang' data from images selection for security reasons
for ($i=0; $i<$this->options->imageCount; $i++) {
unset($this->images[$i]['lang']);
}
}

Expand Down
Loading

0 comments on commit 84d56b7

Please sign in to comment.