Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions Modules/Test/classes/CanAccessFileUploadAnswer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<?php declare(strict_types=1);

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/

namespace ILIAS\Modules\Test;

use ILIAS\Data\Result;
use ILIAS\Data\Result\Ok;
use ILIAS\Data\Result\Error;
use ILIAS\DI\Container;
use ilObjTest;
use ilObject2;
use ilSession;
use ilTestSession;
use ilTestAccess;

class CanAccessFileUploadAnswer
{
/** @var Container */
private $container;
/** @var callable(int) : int */
private $object_id_of_test_id;
/** @var callable(int) : string[] */
private $references_of;
/** @var callable(string) : mixed */
private $session;
/** @var callable(string, int, int) : bool */
private $checkResultsAccess;

/**
* @param Container $container
* @param callable(int) : int $object_id_of_test_id
* @param callable(string) : string[] $references_of
* @param callable(string) : mixed $session
* @param callable(string, int, int) : bool $checkResultsAccess
*/
public function __construct(
Container $container,
$object_id_of_test_id = [ilObjTest::class, '_getObjectIDFromTestID'],
$references_of = [ilObject2::class, '_getAllReferences'],
$session = [ilSession::class, 'get'],
Comment thread
mjansenDatabay marked this conversation as resolved.
callable $checkResultsAccess = null
) {
$this->container = $container;
$this->object_id_of_test_id = $object_id_of_test_id;
$this->references_of = $references_of;
$this->session = $session;
$this->checkResultsAccess = $checkResultsAccess ?? static function (string $reference, int $test_id, int $active_id) : bool {
$access = new ilTestAccess($reference, $test_id);
return $access->checkResultsAccessForActiveId($active_id);
};
}

/**
* @param string $path
*
* @return Result<bool>
*/
public function isTrue(string $path) : Result
{
$path_and_test = $this->pathAndTestId($path);

if (!$path_and_test) {
return new Error('Not a file upload path of test answers.');
Comment thread
mjansenDatabay marked this conversation as resolved.
}
if (!$path_and_test['test']) {
return new Ok(false);
Comment thread
mjansenDatabay marked this conversation as resolved.
}

$object_id = (int) ($this->object_id_of_test_id)($path_and_test['test']);
if (!$object_id) {
return new Ok(false);
Comment thread
mjansenDatabay marked this conversation as resolved.
}

$references = ($this->references_of)($object_id);

return new Ok($this->canRead($references) && $this->roleBasedCheck($path_and_test['test'], $references, $path_and_test['path']));
Comment thread
mjansenDatabay marked this conversation as resolved.
}

private function isAnonymous() : bool
{
return $this->container->user()->isAnonymous() || !$this->container->user()->getId();
}

private function accessCodeOk(string $file, int $test_id) : bool
{
$code = ($this->session)(ilTestSession::ACCESS_CODE_SESSION_INDEX)[$test_id] ?? false;

return $code && $this->userDidUpload($test_id, $file, $code);
}

private function userDidUpload(int $test_id, string $file, string $code = null) : bool
{
$where = [
'user_fi = %s',
'value1 = %s',
'anonymous_id ' . (null === $code ? 'IS' : '=') . ' %s',
'test_fi = %s',
];

$result = $this->container->database()->queryF(
'SELECT 1 FROM tst_solutions INNER JOIN tst_active ON active_id = active_fi WHERE ' . implode(' AND ', $where),
['integer', 'text', 'text', 'integer'],
[$this->container->user()->getId(), $file, $code, $test_id]
);

return (bool) $this->container->database()->numRows($result);
}

private function activeIdOfFile(string $file) : ?int
{
$result = $this->container->database()->queryF(
'SELECT active_id FROM tst_active INNER JOIN tst_solutions WHERE value1 = %s',
['text'],
[$file]
);

$result = $this->container->database()->fetchAssoc($result)['active_id'] ?? false;
if (!$result) {
return null;
}

return (int) $result;
}

/**
* @param int $test_id
* @param string[] $references
* @param string $file
*
* @return bool
*/
private function roleBasedCheck(int $test_id, array $references, string $file) : bool
{
return $this->isAnonymous() ? $this->accessCodeOk($file, $test_id) : $this->canAccessResults($test_id, $references, $file) || $this->userDidUpload($test_id, $file);
}

/**
* @param string[] $references
*
* @return bool
*/
private function canRead(array $references) : bool
{
return $this->checkReferences(function (string $ref_id) : bool {
return $this->container->access()->checkAccess('read', '', $ref_id);
}, $references);
}

/**
* @param int $test_id
* @param string[] $references
* @param string $file
*
* @return bool
*/
private function canAccessResults(int $test_id, array $references, string $file) : bool
{
$active_id = $this->activeIdOfFile($file);
if (!$active_id) {
return false;
}

return $this->checkReferences(function (string $reference) use ($test_id, $active_id) : bool {
return ($this->checkResultsAccess)($reference, $test_id, $active_id);
}, $references);
}

/**
* @param callable(string) : bool $access
* @param string[] $references
*
* @return bool
*/
private function checkReferences(callable $check, array $references) : bool
{
foreach ($references as $ref_id) {
if ($check($ref_id)) {
return true;
}
}

return false;
}

/**
* @param string $path
*
* @return null|array{test: int, path: string}
*/
private function pathAndTestId(string $path) : ?array
{
$results = [];
if (!preg_match(':/assessment/tst_(\d+)/.*/([^/]+)$:', $path, $results)) {
return null;
}

return [
'test' => (int) $results[1],
'path' => $results[2],
];
}
}
11 changes: 11 additions & 0 deletions Modules/Test/classes/class.ilObjTestAccess.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php
/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */

use ILIAS\Modules\Test\CanAccessFileUploadAnswer;

include_once "./Services/Object/classes/class.ilObjectAccess.php";
include_once "./Modules/Test/classes/inc.AssessmentConstants.php";
include_once './Services/Conditions/interfaces/interface.ilConditionHandling.php';
Expand All @@ -19,6 +21,15 @@
*/
class ilObjTestAccess extends ilObjectAccess implements ilConditionHandling
{
public function canBeDelivered(ilWACPath $ilWACPath)
{
global $DIC;

$can_it = (new CanAccessFileUploadAnswer($DIC))->isTrue($ilWACPath->getPath());

return !$can_it->isOk() || $can_it->value();
}

/**
* Checks wether a user may invoke a command or not
* (this method is called by ilAccessHandler::checkAccess)
Expand Down
3 changes: 3 additions & 0 deletions Modules/Test/module.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,7 @@
<pdfpurpose name="PrintViewOfQuestions" preferred="PhantomJS" />
<pdfpurpose name="UserResult" preferred="PhantomJS" />
</pdfpurposes>
<web_access_checker>
<secure_path path="assessment" checking-class="ilObjTestAccess" in-sec-folder='0'/>
</web_access_checker>
</module>
Loading