Commit 88f12d2e authored by Fabrice Gangler's avatar Fabrice Gangler 🎨
Browse files

Comptoir v2.9.2

parents cb4e0b75 5de1c5ae
Pipeline #13451 failed with stage
in 5 minutes and 38 seconds
......@@ -5,6 +5,14 @@ Tous les changements notables de ce projet sont documentés dans ce fichier.
Le format s'appuie sur [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
et le projet suit [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [2.9.2](https://gitlab.adullact.net/Comptoir/Comptoir-srv/tags/v2.9.2) - 2021-03-23
### Fixed
- [#951](https://gitlab.adullact.net/Comptoir/Comptoir-srv/issues/951) Formulaire inscription : ajout d'un jeton + temporisation pour diminuer le spam
- [#643](https://gitlab.adullact.net/Comptoir/Comptoir-srv/issues/643) Tests fonctionnels: les tests de création de compte sont maintenant idempotent
## [2.9.1](https://gitlab.adullact.net/Comptoir/Comptoir-srv/tags/v2.9.1) - 2021-03-18
### Fixed
......@@ -13,6 +21,7 @@ et le projet suit [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
- [#950](https://gitlab.adullact.net/Comptoir/Comptoir-srv/issues/950) Accessibilité - Ajouter une balise `<header>` (RGAA 9.2.1)
- [#947](https://gitlab.adullact.net/Comptoir/Comptoir-srv/issues/947) Vagrant - Forcer l'utilisation de composer 1.x
## [2.9.0](https://gitlab.adullact.net/Comptoir/Comptoir-srv/tags/v2.9.0) - 2020-06-26
### Added
......
......@@ -14,7 +14,7 @@ Once the code in `develop` is ready, merge into `master` like follows:
```sh
git checkout master
MYTAG="vX.Y.Z"
git merge develop --no-ff -m "Comptoir $MYTAG"
git merge develop --no-ff --no-verify -m "Comptoir $MYTAG"
git tag -a $MYTAG -m "$MYTAG"
git push origin master
git push origin $MYTAG
......
# How to upgrade from v2.9.1 to v2.9.2
## 1) First backup (DB + images files)
```bash
# (1) user comptoir
sudo su comptoir
# First backup (DB + images files)
/home/comptoir/Comptoir-srv/bin/COMPTOIR_export_DB_data_AND_images.sh
/home/comptoir/Comptoir-srv/bin/COMPTOIR_export_DB_structure_only.sh
ls -lh /home/comptoir/Comptoir-EXPORT/
```
## 2) Preloading vendor directory
```bash
# (1) use the user "comptoir"
sudo su comptoir
# Preloading vendor
mkdir /home/comptoir/tmp_migration
cd /home/comptoir/tmp_migration
git clone https://gitlab.adullact.net/Comptoir/Comptoir-srv.git
mv Comptoir-srv/ Comptoir_tmp_update_vendor
cd Comptoir_tmp_update_vendor/
git checkout origin/master
git log --decorate --oneline --graph --all
composer check-platform-reqs
composer validate
composer install
```
## 3) Activate maintenance site
see:
[www-maintenance-comptoir](https://gitlab.adullact.net/Adullact-prive/comptoir-prive/www-maintenance-comptoir)
(private repository)
```bash
# (1) use your user
# Activate maintenance site
sudo a2dissite 25-https_comptoir-du-libre.org.conf
sudo a2ensite 30-503-MAINTENANCE_https_comptoir-du-libre.org.conf
sudo service apache2 reload
# on your computer, the following command lines
# must retrun an HTTP 500 response
curl -v https://comptoir-du-libre.org/
curl -v https://comptoir-du-libre.org/notFoundPage
```
## 4) Backup before migration (DB + images files + main directory)
```bash
# (1) user comptoir
sudo su comptoir
# Second backup (DB + images files)
/home/comptoir/Comptoir-srv/bin/COMPTOIR_export_DB_data_AND_images.sh
/home/comptoir/Comptoir-srv/bin/COMPTOIR_export_DB_structure_only.sh
ls -lh /home/comptoir/Comptoir-EXPORT/
# Backup main directory
cd /home/comptoir/
DATE=$(date +"%Y.%m.%d_%Hh%M")
tar -czvf "Comptoir-srv_BACKUP_${DATE}.tar.gz" Comptoir-srv
```
## 5) Update source code
```bash
# (1) user comptoir
sudo su comptoir
# Update source code
cd /home/comptoir/Comptoir-srv
rm -rvf vendor/
git remote -v
git remote update -p
git checkout origin/master
git log --decorate --oneline --graph --all
```
## 6) Update vendor directory + clean cache
```bash
# (1) user comptoir
sudo su comptoir
# Update vendor directory (see step 2 "Preloading vendor directory")
cd /home/comptoir/Comptoir-srv
rm -rvf vendor/
mv -v ../tmp_migration/Comptoir_tmp_update_vendor/vendor ./
# Clean cache
cd /home/comptoir/Comptoir-srv
bin/cake cache clear_all
```
## 7) Apply migration of the database (new tables, adding fields, ...)
The following changes must be recorded in the [production version tracking](https://gitlab.adullact.net/Adullact-prive/comptoir-prive/Exploitation-Comptoir/-/blob/master/Suivi-versions-PROD.md) file.
```bash
# (1) user comptoir
sudo su comptoir
# Apply migration of the database (new tables, adding fields, ...)
cd /home/comptoir/Comptoir-srv
bin/cake migrations migrate
# Now you can apply additional changes in SQL with pgsl
# ---> nothing to do here for this version
```
## 8) Update config files
The following changes must be recorded in the [production version tracking](https://gitlab.adullact.net/Adullact-prive/comptoir-prive/Exploitation-Comptoir/-/blob/master/Suivi-versions-PROD.md) file.
nothing to do here for this version
```bash
# (1) user comptoir
sudo su comptoir
# Apply additional changes in config files
cd /home/comptoir/Comptoir-srv
vim config/comptoir.php # ---> nothing to do here for this version
vim config/app.php # ---> nothing to do here for this version
```
## 9) Enable the main website
see:
[www-maintenance-comptoir](https://gitlab.adullact.net/Adullact-prive/comptoir-prive/www-maintenance-comptoir)
(private repository)
```bash
# (1) use your user
# Enable the main website
sudo a2dissite 30-503-MAINTENANCE_https_comptoir-du-libre.org.conf
sudo a2ensite 25-https_comptoir-du-libre.org.conf
sudo service apache2 reload
# on your computer, the first following command line
# must return an HTTP 200 response code
# and the second return an HTTP 404 response code.
curl -v https://comptoir-du-libre.org/
curl -v https://comptoir-du-libre.org/notFoundPage
```
## 10) Backup after upgraded
```bash
# (1) user comptoir
sudo su comptoir
# Backup after upgraded (DB + images files)
/home/comptoir/Comptoir-srv/bin/COMPTOIR_export_DB_data_AND_images.sh
/home/comptoir/Comptoir-srv/bin/COMPTOIR_export_DB_structure_only.sh
ls -lh /home/comptoir/Comptoir-EXPORT/
```
## 11) Clean up
```bash
# (1) user comptoir
sudo su comptoir
# Clean up
cd /home/comptoir/
rm -rvf tmp_migration/
rm -v Comptoir-srv_BACKUP_*.tar.gz
```
......@@ -281,7 +281,7 @@ Configure::write(
Configure::write(
'VERSION',
[
"footer" => "v2.9.1"
"footer" => "v2.9.2"
]
);
......
......@@ -464,21 +464,153 @@ class UsersController extends AppController
}
/**
* Tooling for "add" method: create anti-spam tokens
* - create anti-spam tokens (get, post, form)
* - save tokens in user session
* - return token for HTTP GET method
*
* @return string token for HTTP GET method
*/
private function addUserCreateTokens()
{
$prefixToken = bin2hex(openssl_random_pseudo_bytes(12));
$getSuffixToken = bin2hex(openssl_random_pseudo_bytes(12));
$postSuffixToken = bin2hex(openssl_random_pseudo_bytes(12));
$formSuffixToken = bin2hex(openssl_random_pseudo_bytes(12));
$dataToken = $this->request->session()->read('CreateUserAntiSpam');
if (is_null($dataToken)) {
$dataToken = [];
}
$dataToken["$prefixToken"] = [
"date" => time(),
"getToken" => "$prefixToken-$getSuffixToken",
"postToken" => "$prefixToken-$postSuffixToken",
"formToken" => "$prefixToken-$formSuffixToken",
];
$this->request->session()->write('CreateUserAntiSpam', $dataToken);
return "$prefixToken-$getSuffixToken";
}
/**
* Tooling for "add" method: get current anti-spam token based on current HTTP method
* @return string
*/
private function addUserGetCurrentToken()
{
$method = strtolower($this->request->method());
if ($method === 'get') {
return strip_tags($this->request->query('t1'));
} elseif ($method === 'post') {
return strip_tags($this->request->query('t2'));
}
return ''; // wrong HTTP method
}
/**
* Tooling for "add" method: check if current anti-spam token in URL is valid
* @return bool
*/
private function addUserIsValidTokenInUrl()
{
$token = $this->addUserGetCurrentToken();
if (empty($token)) {
return false; // wrong HTTP method or token parameter in the URL does not exist
} else {
$method = strtolower($this->request->method());
$prefixToken = $this->addUserGetPrefixToken($token);
$availableTokens = $this->request->session()->read('CreateUserAntiSpam');
if (!isset($availableTokens[$prefixToken])) {
return false;
} elseif ($availableTokens[$prefixToken][$method .'Token'] !== $token) {
return false;
}
}
return true;
}
/**
* Tooling for "add" method: get anti-spam prefix token
*
* @param string $token
* @return mixed|string
*/
private function addUserGetPrefixToken($token)
{
$tokenData = explode('-', $token);
return $tokenData[0];
}
/**
* Tooling for "add" method: get current token by type ('get', 'post' or 'form')
*
* @param string $type 'get', 'post' or 'form'
* @return string
*/
private function addUserGetTokenByType(string $type)
{
$prefixToken = $this->addUserGetPrefixToken($this->addUserGetCurrentToken());
$availableTokens = $this->request->session()->read('CreateUserAntiSpam');
$token = '';
if (isset($availableTokens["$prefixToken"][$type.'Token'])) {
$token = $availableTokens["$prefixToken"][$type.'Token'];
}
return $token;
}
/**
* Tooling for "add" method: check if form is sent too quicky
* @return bool
*/
private function addUserIsFormSentTooQuickly()
{
$prefixToken = $this->addUserGetPrefixToken($this->addUserGetCurrentToken());
$availableTokens = $this->request->session()->read('CreateUserAntiSpam');
if (isset($availableTokens["$prefixToken"])) {
$tokenTimestamp = $availableTokens["$prefixToken"]['date'];
$diffTimestamp = time() - $tokenTimestamp;
if ($diffTimestamp >= 4) { // @@@TODO magic number !!!
return false;
}
}
return true;
}
/**
* Add method
*
* @return void Redirects on successful add, renders view otherwise.
*/
public function add()
{
$message = "";
$lang = $this->selectedLanguage;
if (!empty($this->request->data)) {
$user = $this->Users->newEntity($this->request->data);
} else {
$user = $this->Users->newEntity();
}
$message = "";
// Force token-based URL (HTTP GET and POST methods), expect for json API
if (!$this->request->is('json') && $this->addUserIsValidTokenInUrl() === false) {
return $this->redirect("/$lang/users/add?t1=". $this->addUserCreateTokens());
}
// Process form data
if ($this->request->is('post')) {
// Check that it's not a robot (expected token and speed submit), expect for json API
if (!$this->request->is('json')) {
$expectedFormToken = $this->addUserGetTokenByType('form');
$formToken = $this->request->data("formToken");
if ($expectedFormToken !== $formToken | $this->addUserIsFormSentTooQuickly()) {
return $this->redirect("/$lang/users/add?t1=". $this->addUserCreateTokens());
}
}
// do not allow to upload an avatar when creating an account
$this->request->data['photo'] = null;
......@@ -506,6 +638,7 @@ class UsersController extends AppController
if ($this->Users->save($user)) {
$message = "Success";
$this->Flash->success(__d("Forms", "Your are registred on the Comptoir du Libre, welcome !"));
$this->request->session()->write('CreateUserAntiSpam', []); // Unset tokens
if (!$this->request->is('json')) {
$currentUser = $this->Auth->identify();
$currentUser["user_type"] = $this->Users
......@@ -514,7 +647,6 @@ class UsersController extends AppController
$this->Auth->setUser($currentUser);
// REDIRECTS TO ---> /<lang>/users/<idUser>
$lang = $this->selectedLanguage;
return $this->redirect("/$lang/users/" . $user->get('id'));
// REDIRECTS TO ---> /api/v1/users/view/<idUser>
// return $this->redirect(['action' => 'view', $user->get('id')]);
......@@ -527,7 +659,16 @@ class UsersController extends AppController
}
$this->ValidationRules->config('tableRegistry', "Users");
$rules = $this->ValidationRules->get();
$tokenForForm = $this->addUserGetTokenByType('form');
$formUrl = "/$lang/users/add?t2=" . $this->addUserGetTokenByType('post');
$userTypes = $this->Users->UserTypes->find('list', ['limit' => 200]);
if (!$this->request->is('json')) {
$this->set(compact(['formUrl']));
$this->set(compact(['tokenForForm']));
}
$this->set(compact('user', 'userTypes', 'rules', 'message'));
$this->set('_serialize', ['user', 'userTypes', 'rules', 'message', 'errors']);
......
......@@ -14,7 +14,15 @@ $this->assign('title', __d("Forms", "Create an account {0}", $myMessage));
<div class="row">
<div class = "col-xs-offset-3 col-xs-6">
<?= $this->Form->create($user, ['type' => 'file', "enctype" => "multipart/form-data", 'id' => "createAccountForm"]) ?>
<?= $this->Form->create($user, ['url' => "$formUrl", 'type' => 'file', "enctype" => "multipart/form-data", 'id' => "createAccountForm"]) ?>
<?= $this->Form->input('formToken',
[
'value' => $tokenForForm,
'empty' => false,
"type"=>"hidden",
"required"=>"required",
"escape"=>false,
]);?>
<fieldset>
<legend><?= "<h1>" . __d("Forms",'Create an account') . "</h1>" ?></legend>
<div class = "form-group">
......
......@@ -36,6 +36,22 @@ class AcceptanceTester extends Actor
use _generated\AcceptanceTesterActions;
private $lang = 'en';
private static $random;
/**
* Return self::$random (static property)
* and initialize it if necessary
*
* @return string
*/
public function getCurrentRandom()
{
if (is_null(self::$random)) {
self::$random = bin2hex(openssl_random_pseudo_bytes(6));
}
return self::$random;
}
/**
* With $email as login,
......@@ -106,13 +122,21 @@ class AcceptanceTester extends Actor
$I = $this;
$I->amOnPage('/');
$I->click('//nav/ul[2]/li[2]/a');
$I->seeInCurrentUrl('/users/add');
$I->seeInCurrentUrl('/users/add?t1=');
$random = $I->getCurrentRandom();
$randomUsername = "$username"."_$random";
$randomEmail = str_replace("@", "$random@", "$email");
// echo "\n-------------\n$randomUsername\n$randomEmail\n---------\n";
// Add delay to look like a real user and not a robot
sleep(4); // @@@TODO magic number !!!
$I->submitForm(
'#createAccountForm',
[
'user_type_id' => $userTypeId,
'username' => $username,
'email' => $email,
'username' => $randomUsername,
'email' => $randomEmail,
'password' => $password,
'confirm_password' => $confirmPwd,
]
......
......@@ -43,48 +43,67 @@ class BasicTestsAccountsCest
/**
* Tests the creation functionality as an Association user named Association1
*
* @param AcceptanceTester $I codeception variable
* @group createUser
* @group createUserAssociation
* @group userAccountAssociation
* @group userAccount
* @group user
*
* @param AcceptanceTester $I codeception variable
* @return void
*/
public function createAccountAsAssociation(AcceptanceTester $I)
{
$ramdom = $I->getCurrentRandom();
$I->createAccountMinimal('6', 'Association1', 'asso@comptoir-du-libre.org', 'mdptest', 'mdptest');
$I->seeElement('div.message.success');
$I->dontSeeInCurrentUrl('/users/add');
$I->canSeeInCurrentUrl('/users/'); // url: /users/<idUser> --> how to really test it?
$I->see('Association1');
$I->see("Association1_$ramdom");
}
/**
* Tests the login functionality as an Association named Association1
*
* @group userLogin
* @group userLoginAssociation
* @group userAccountAssociation
* @group userAccount
* @group user
*
* @param AcceptanceTester $I codeception variable
* @return void
*/
public function tryToLoginAsAssociation(AcceptanceTester $I)
{
$lang = $this->lang;
$userName = 'Association1';
$I->loginMe('asso@comptoir-du-libre.org', 'mdptest', $userName);
$ramdom = $I->getCurrentRandom();
$userName = "Association1_$ramdom";
$I->loginMe("asso$ramdom@comptoir-du-libre.org", 'mdptest', $userName);
$I->seeElement('div.message.success');
$I->canSee('Association1');
$I->canSee("Association1_$ramdom");
$I->cantSeeElement("//a[@href=\"/$lang/users/login\"]");
}
/**
* Tests the logout functionality as an Association account named Association1
*
* @param AcceptanceTester $I codeception variable
* @group userLogout
* @group userLogoutAssociation
* @group userAccountAssociation
* @group userAccount
* @group user
*
* @param AcceptanceTester $I codeception variable
* @return void
*/
public function checkLougoutAssociation(AcceptanceTester $I)
{
$lang = $this->lang;
$I->loginMe('asso@comptoir-du-libre.org', 'mdptest', 'Association1');
$I->logoutMe('Association1');
$I->cantSee('Association1');
$ramdom = $I->getCurrentRandom();
$I->loginMe("asso$ramdom@comptoir-du-libre.org", 'mdptest', "Association1_$ramdom");
$I->logoutMe("Association1_$ramdom");
$I->cantSee("Association1_$ramdom");
$I->seeElement("//a[@href=\"/$lang/users/login\"]");
}
......@@ -95,48 +114,72 @@ class BasicTestsAccountsCest
/**
* Tests the account creation functionality as a Person named Person1
*
* @param AcceptanceTester $I codeception variable
* @group createUser
* @group createUserPerson
* @group userAccountPerson
* @group userAccount
* @group user
*
* @param AcceptanceTester $I codeception variable
* @return void
*/
public function createAccountAsPerson(AcceptanceTester $I)
{
$I->createAccountMinimal('4', 'Person1', 'person@comptoir-du-libre.org', 'mdptest', 'mdptest');
$ramdom = $I->getCurrentRandom();
$I->createAccountMinimal(
'4',
'Person1',
'person@comptoir-du-libre.org',
'mdptest',
'mdptest'
);
$I->seeElement('div.message.success');
$I->dontSeeInCurrentUrl('/users/add');
$I->canSeeInCurrentUrl('/users/'); // url: /users/<idUser> --> how to really test it?
$I->see('Person1');
$I->see("Person1_$ramdom");
}
/**
* Tests the login functionality as a Person account named Person1
*
* @param AcceptanceTester $I codeception variable
* @group userLogin
* @group userLoginPerson
* @group userAccountPerson
* @group userAccount
* @group user
*
* @param AcceptanceTester $I codeception variable
* @return void
*/
public function tryToLoginAsPerson(AcceptanceTester $I)
{
$ramdom = $I->getCurrentRandom();
$lang = $this->lang;
$I->loginMe('person@comptoir-du-libre.org', 'mdptest', 'Person1');
$I->loginMe("person$ramdom@comptoir-du-libre.org", 'mdptest', "Person1_$ramdom");
$I->seeElement('div.message.success');