Skip to content

Commit

Permalink
Merge pull request #3 from torque-foxes/feature/mms-104-lockout-message
Browse files Browse the repository at this point in the history
MMS-104: Add lockout session logic
  • Loading branch information
scott1702 committed Sep 10, 2019
2 parents 66c72d4 + f016fa7 commit 5b3860f
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 32 deletions.
33 changes: 32 additions & 1 deletion src/controllers/PartialSubmissionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Control\Session;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\ValidationException;
use SilverStripe\UserForms\Model\EditableFormField;

Expand Down Expand Up @@ -80,7 +82,8 @@ public function savePartialSubmission(HTTPRequest $request)
}

// Refresh session ID
$request->getSession()->set(self::SESSION_KEY, $submissionID);
static::reloadSession($request->getSession(), $submissionID);

foreach ($postVars as $field => $value) {
/** @var EditableFormField $editableField */
$editableField = $this->createOrUpdateSubmission([
Expand All @@ -106,6 +109,34 @@ public function savePartialSubmission(HTTPRequest $request)
return new HTTPResponse($return, ($return ? 201 : 400));
}

/**
* Reload session for partial submissions
* @param Session $session
* @param int $sessionID Partial form submission ID
* @throws ValidationException
*/
public static function reloadSession($session, $sessionID)
{
$partial = PartialFormSubmission::get()->byID($sessionID);
if (!$partial) {
return;
}

$session->set(self::SESSION_KEY, $partial->ID);

$now = new \DateTime(DBDatetime::now()->getValue());
$now->add(new \DateInterval('PT30M'));

$phpSessionID = session_id();
if (!$phpSessionID) {
return;
}

$partial->LockedOutUntil = $now->format('Y-m-d H:i:s');
$partial->PHPSessionID = $phpSessionID;
$partial->write();
}

/**
* @param $formData
* @return DataObject|EditableFormField
Expand Down
80 changes: 59 additions & 21 deletions src/controllers/PartialUserFormController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
use Exception;
use Firesphere\PartialUserforms\Models\PartialFormSubmission;
use Page;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use Firesphere\PartialUserforms\Forms\PasswordForm;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Control\Middleware\HTTPCacheControlMiddleware;
use SilverStripe\Dev\Debug;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\HiddenField;
use SilverStripe\ORM\DataObject;
Expand Down Expand Up @@ -63,15 +65,23 @@ public function partial(HTTPRequest $request)
// or another submission has started
$sessionID = $request->getSession()->get(PartialSubmissionController::SESSION_KEY);
if (!$sessionID || $sessionID !== $partial->ID) {
$request->getSession()->set(PartialSubmissionController::SESSION_KEY, $partial->ID);
PartialSubmissionController::reloadSession($request->getSession(), $partial->ID);
}

// Verify user
if ($controller->PasswordProtected &&
$request->getSession()->get(PasswordForm::PASSWORD_SESSION_KEY) !== $partial->ID
) {
return $this->redirect('verify');
}

// Lock form
if ($this->isLockedOut()) {
// TODO: MMS-115
Debug::dump('This form is currently being used by someone else. Please try again in 30 minutes.');
// Redirect to overview page
}

$form = $controller->Form();
$form->loadDataFrom($partial->getFields());
$this->populateData($form, $partial);
Expand Down Expand Up @@ -103,6 +113,33 @@ public function partial(HTTPRequest $request)
])->renderWith([static::class, Page::class]);
}

/**
* A little abstraction to be more readable
*
* @param HTTPRequest $request
* @return PartialFormSubmission|void
* @throws HTTPResponse_Exception
*/
public function validateToken($request)
{
// Ensure this URL doesn't get picked up by HTTP caches
HTTPCacheControlMiddleware::singleton()->disableCache();

$key = $request->param('Key');
$token = $request->param('Token');
if (!$key || !$token) {
return $this->httpError(404);
}

/** @var PartialFormSubmission $partial */
$partial = PartialFormSubmission::validateKeyToken($key, $token);
if ($partial === false) {
return $this->httpError(404);
}

return $partial;
}

/**
* Add partial submission and set the uploaded filenames as right title of the file fields
*
Expand Down Expand Up @@ -145,29 +182,30 @@ protected function populateData($form, $partial)
}

/**
* A little abstraction to be more readable
*
* @param HTTPRequest $request
* @return PartialFormSubmission|void
* @throws HTTPResponse_Exception
* Checks whether this form is currently being used by someone else
* @return bool
* @throws \SilverStripe\ORM\ValidationException
*/
public function validateToken($request)
protected function isLockedOut()
{
// Ensure this URL doesn't get picked up by HTTP caches
HTTPCacheControlMiddleware::singleton()->disableCache();

$key = $request->param('Key');
$token = $request->param('Token');
if (!$key || !$token) {
return $this->httpError(404);
}

/** @var PartialFormSubmission $partial */
$partial = PartialFormSubmission::validateKeyToken($key, $token);
if ($partial === false) {
return $this->httpError(404);
$session = $this->getRequest()->getSession();
$submissionID = $session->get(PartialSubmissionController::SESSION_KEY);
$partial = PartialFormSubmission::get()->byID($submissionID);
$phpSessionID = session_id();

// If invalid sessions or if the last session was from the same user or that the recent session has expired
if (
!$submissionID ||
!$partial ||
!$partial->PHPSessionID ||
$phpSessionID === $partial->PHPSessionID ||
$partial->dbObject('LockedOutUntil')->InPast()
) {
PartialSubmissionController::reloadSession($session, $partial->ID);
return false;
}

return $partial;
// Lockout when there's an ongoing session
return $phpSessionID !== $partial->PHPSessionID && $partial->dbObject('LockedOutUntil')->InFuture();
}
}
2 changes: 1 addition & 1 deletion src/extensions/UserDefinedFormControllerExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,6 @@ protected function createPartialSubmission()
}

// Refresh session on start
$page->getRequest()->getSession()->set(PartialSubmissionController::SESSION_KEY, $submissionID);
PartialSubmissionController::reloadSession($page->getRequest()->getSession(), $submissionID);
}
}
14 changes: 9 additions & 5 deletions src/models/PartialFormSubmission.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,12 @@ class PartialFormSubmission extends SubmittedForm
* @var array
*/
private static $db = [
'IsSend' => 'Boolean(false)',
'TokenSalt' => 'Varchar(16)',
'Token' => 'Varchar(16)',
'Password' => 'Varchar(64)',
'IsSend' => 'Boolean(false)',
'TokenSalt' => 'Varchar(16)',
'Token' => 'Varchar(16)',
'Password' => 'Varchar(64)',
'LockedOutUntil' => 'Datetime',
'PHPSessionID' => 'Varchar(128)'
];

/**
Expand Down Expand Up @@ -124,7 +126,9 @@ public function getCMSFields()
'UserDefinedFormID',
'Submitter',
'PartialUploads',
'Password'
'Password',
'LockedOutUntil',
'PHPSessionID'
]);

$partialFields = $this->PartialFields();
Expand Down
1 change: 0 additions & 1 deletion tests/unit/PartialFieldSubmissionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Firesphere\PartialUserforms\Models\PartialFormSubmission;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Security\DefaultAdminService;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
use SilverStripe\UserForms\Model\UserDefinedForm;

Expand Down
1 change: 0 additions & 1 deletion tests/unit/PartialFileFieldSubmissionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use Firesphere\PartialUserforms\Models\PartialFileFieldSubmission;
use Firesphere\PartialUserforms\Models\PartialFormSubmission;
use SilverStripe\Assets\File;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Security\DefaultAdminService;
use SilverStripe\Security\Security;
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/PartialSubmissionControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\UserForms\Model\EditableFormField\EditableFileField;
use SilverStripe\UserForms\Model\UserDefinedForm;

Expand Down Expand Up @@ -212,6 +213,32 @@ public function testSaveDataWithExpiredSession()
$this->assertEquals(404, $response->getStatusCode());
}

public function testReloadSession()
{
$sessionID = $this->session()->get(PartialSubmissionController::SESSION_KEY);
$this->assertNotEmpty($sessionID);

$partial = PartialFormSubmission::get()->byID($sessionID);
$this->assertNull($partial->LockedOutUntil);
$this->assertNull($partial->PHPSessionID);

$this->session()->clear(PartialSubmissionController::SESSION_KEY);
$sessionID = $this->session()->get(PartialSubmissionController::SESSION_KEY);
$this->assertNull($sessionID);

// New session
session_id('petrichor');
DBDatetime::set_mock_now('2019-02-15 10:00:00');
$id = $this->savePartial(['PartialID' => 1]); // save submission reloads the session
$partial = PartialFormSubmission::get()->byID($id);

$this->assertEquals('2019-02-15 10:30:00', $partial->LockedOutUntil);
$this->assertEquals('petrichor', $partial->PHPSessionID);

// Set the session back to empty string to prevent destroying uninitialized session
session_id('');
}

/**
* Helper function for saving partial submission
* @param array $values
Expand Down
8 changes: 7 additions & 1 deletion tests/unit/PartialUserFormControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Firesphere\PartialUserforms\Tests;

use Firesphere\PartialUserforms\Forms\PasswordForm;
use Firesphere\PartialUserforms\Models\PartialFormSubmission;
use SilverStripe\Assets\File;
use SilverStripe\Dev\FunctionalTest;
Expand Down Expand Up @@ -97,6 +96,13 @@ public function testPasswordProtectedPartial()
$this->assertContains($formOpeningTag, $result->getBody());
}

/**
* TODO: Add test when MMS-115 is complete
*/
public function testIsLockedOut()
{
}

public function setUp()
{
parent::setUp();
Expand Down
1 change: 0 additions & 1 deletion tests/unit/PartialUserFormVerifyControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use SilverStripe\Control\NullHTTPRequest;
use SilverStripe\Control\Session;
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\Dev\SapphireTest;

class PartialUserFormVerifyControllerTest extends FunctionalTest
{
Expand Down

0 comments on commit 5b3860f

Please sign in to comment.