Commit 7e96a44b authored by okhin's avatar okhin 🚴

Merge branch 'preprod' into 'master'

Mise en production

See merge request !37
parents 0185770e d131a2fc
Pipeline #2144 failed with stages
in 1 minute and 24 seconds
/nbproject/private/
*~
messages.pot
locales/es_ES/LC_MESSAGES/messages.mo
locales/de_DE/LC_MESSAGES/messages.mo
locales/en_US/LC_MESSAGES/messages.mo
locales/fr_FR/LC_MESSAGES/messages.mo
locales/it_IT/LC_MESSAGES/messages.mo
/.tx/
#images/logo-lqdn-2.png
static/images/logo-lqdn.png
images/ids/
images/logo-[0-9]*.png
images/banner1.png
images/banner_fr_FR.png
images/banner_en_US.png
videos/
admin/.htaccess
admin/.htpasswd
*.mo
pplome.*
*.log
tmp/*
admin/tmp/*
piplomes/
CACHE/
vendor/
pdf/
/phinx.yml
/app/env
app/env
.php_cs.cache
.DS_Store
__MAC
tests/_output/*
/var/log/*
!/var/log/.gitkeep
/.php_cs.cache
tests/*/_generated/*
tests/_data
tests/_output
tests/_support/_generated
......@@ -9,6 +9,7 @@ stages:
job test:
stage: test
variables:
GIT_SUBMODULE_STRATEGY: normal
SQL_HOST: "localhost"
SQL_PORT: "3306"
SQL_DATABASE: "test_$CI_BUILD_REF"
......@@ -44,7 +45,7 @@ job test:
- echo "SQL_DATABASE=${SQL_DATABASE}" >> app/env
- make install
- make server-start
- make test
- make -k test
- make server-stop
- mysql -u $SQL_USER -p$SQL_PASSWORD -e "DROP DATABASE $SQL_DATABASE"
tags:
......
[submodule "www/static/pi-billion"]
path = www/static/pi-billion
url = https://git.laquadrature.net/lqdn-interne/pi-billion.git
......@@ -31,10 +31,16 @@ doctor: ## Check that everything is installed to use this application
@which msgfmt >/dev/null 2>&1 && echo "\033[32mmsgfmt installed\033[0m" || echo "\033[31mmsgfmt not installed\033[0m"
@which xgettext >/dev/null 2>&1 && echo "\033[32mxgettext installed\033[0m" || echo "\033[31mxgettext not installed\033[0m"
@which pdftk >/dev/null 2>&1 && echo "\033[32mpdftk installed\033[0m" || echo "\033[31mpdftk not installed\033[0m"
@test -s /usr/share/php7.0-xml/xml/dom.ini && echo "\033[032mphp-ext-dom installed\033[0m" || echo "\033[31mphp-ext-dom not installed (php-xml)\033[0m"
@test -s /usr/share/php7.0-zip/zip/zip.ini && echo "\033[032mphp-zip installed\033[0m" || echo "\033[31mphp-zip not installed\033[0m"
@test -s /usr/share/php7.0-mbstring/mbstring/mbstring.ini && echo "\033[032mphp-mbstring installed\033[0m" || echo "\033[31mphp-mbstring not installed\033[0m"
@test -s /usr/share/php7.0-curl/curl/curl.ini && echo "\033[032mphp-curl installed\033[0m" || echo "\033[31mphp-curl not installed\033[0m"
install: ## Install the application
@echo "\033[1m\033[36m==> Install Composer dependencies\033[0m\033[21m"
@composer -n install
@mkdir -p ./log
@mkdir -p ./tmp
reset-db: ## Install or re-install the DB
@echo "\033[1m\033[36m==> Drop database "$(SQL_DATABASE)" if it already exists\033[0m\033[21m"
......@@ -56,12 +62,16 @@ test-functional: ## Launch functional tests.
@$(MAKE) reset-db
@./vendor/bin/codecept run functional
coverage: ## Launch functional tests with coverage.
@$(MAKE) reset-db
@./vendor/bin/codecept run functional --coverage-html
server-start: server-stop ## Launch a local server
@php -S 127.0.0.1:8000 >> ./var/log/server.log &
@php -S 127.0.0.1:8000 -t ./www/ >> ./log/server.log &
@echo "\033[32mServer running. (http://127.0.0.1:8000)\033[0m"
server-stop: ## Stop local server if running
@ps -aux | grep "[p]hp -S 127.0.0.1:8000" | grep -v grep | awk '{print $$2}' | xargs -r -n 1 kill
-@ps -aux | grep "[p]hp -S 127.0.0.1:8000" | grep -v grep | awk '{print $$2}' | xargs -r -n 1 kill
@echo "\033[32mServer stopped. (http://127.0.0.1:8000)\033[0m"
cs-fix: ## Fix CS
......
# LQDN soutien
## Installation
This is a php application based on Fat-Free-Framework and a MySQL database that handles donations and presents offered to donors to La Quadrature du Net
It's used as our primary donation platform, and uses our bank's payment system (cheaper) to get one-time or recurring payments recorded into the donation platform.
## Installation (quick)
In order to install this project, run `make doctor` to check that everything is fine.
......@@ -19,3 +23,52 @@ To access to the admin (`/admin`) use `admin`/`password`
## Contributing
Before submitting a PR, makes sure tests are OK: `make test`
# Detailed dependencies, installation ...
## Dependencies
This software depends on a few software :
* **composer** (to install php dependencies) see https://getcomposer.org/
* a **mysql client** (to inject the database schema)
* a **mysql server** (no need for it to be local)
* **pdftk** to generate pi-plomes (pdf with pi's decimals offered to our donors)
* **texlive-latex-base** for the `pdflatex` binary for the piplomes (only required in production or for people debugging the piplomes code)
and for developers you may also need :
* **xgettext** to manage translations, use:
* `make messages.pot` to update PO files from source code.
* `make translations` to compile MO files from PO files when translations are ready.
This software uses:
* **PHP5.6** currently (migration to PHP7.x is in the pipe)
* **phinx** to inject database schema and initial data / accounts into MySQL see https://phinx.org/
* **php-cs-fixer** to check php code for errors and fix coding standard issues, see https://github.com/FriendsOfPHP/PHP-CS-Fixer
* **doctrine/dbal** as ORM see https://github.com/doctrine/dbal
* **f3 framework** as main view / controller framework. see https://fatfreeframework.com/
Look at `app/` folder for most Controllers, and `app/routes.ini` for the application routes (the URLs)
# Production deployment (not finished)
* `app/env` must contains the proper values : database connection, bank visa payment codes, and ENV=production at the bottom.
* point your Nginx or Apache with PHP5.6 to the `www/` folder, and either allow Rewrite Rules (a2enmod rewrite) or point every URL not being a file to index.php (see below for an nginx sample)
* use AND FORCE https usage, it's 2018, people ;)
Nginx configuration sample:
```
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php5.sock;
}
location / {
# try to serve file directly, fallback to index.php
try_files $uri /index.php$is_args$args;
}
```
......@@ -19,9 +19,9 @@ Cette fonction définit un mot de passe de 12 caractères
}
/*
Cette fonction cherche à nettoyer un mauvais encodage en utf-8.
*/
/*
Cette fonction cherche à nettoyer un mauvais encodage en utf-8.
*/
public static function clean_encoding($string)
{
$bad_encoding = array("é","à ","è","ç","ë","ô","î","ù");
......
[globals]
; Répertoire contenant les classes chargées dynamiquement
AUTOLOAD=app/
AUTOLOAD=../app/
; Répertoire contenant les templates
UI=templates/
UI=../app/view/
; Répertoire des fichiers temporaires
TEMP=../tmp/
; Répertoire des logs
LOGS=tmp
LOGS=../log/
; Gestionnaire d'erreur personnalisé
; ONERROR="Main->error"
......
......@@ -46,6 +46,14 @@ define("SMTP_PW", getenv('SMTP_PW'));
define("DEBUG", getenv('DEBUG'));
define("SENTRY", getenv('SENTRY'));
$sentry_client = new Raven_Client($SENTRY);
$error_handler = new Raven_ErrorHandler($sentry_client);
$error_handler->registerExceptionHandler();
$error_handler->registerErrorHandler();
$error_handler->registerShutdownFunction();
if ('prod' !== $env) {
error_reporting(E_ALL|E_WARNING);
ini_set("display_errors", true);
......
This diff is collapsed.
<?php
namespace Controller;
class Bank extends Controller
{
/* We want to ignore the sanitizing on this page */
public function sanitizeForms($f3)
{
return;
}
public function sanitizeForms($f3)
{
return;
}
/*
Page de retour de la banque
*/
......@@ -14,7 +15,7 @@ class Bank extends Controller
{
@include_once("config.php");
$cb_log = new Log('/cb.log');
$cb_log = new \Log('/cb.log');
$error="";
$result = $f3->get('POST.vads_result');
$status = $f3->get('POST.vads_trans_status');
......@@ -80,9 +81,11 @@ class Bank extends Controller
$sig .= $value . "+";
}
$sig .= CERTIFICATE;
$cb_log->write("sig: " . $signature . " == " . sha1($sig));
if (sha1($sig)!=$signature) {
$error = "Error in signature: " . $signature . " != " . sha1($sig);
### Attempt to do it in hmac-sha256
$sig_hash = base64_encode(hash_hmac('sha256', $sig, CERTIFICATE, true));
$cb_log->write("sig: " . $signature . " == " . $sig_hash);
if ($sig_hash!=$signature) {
$error = "Error in signature: " . $signature . " != " . $sig_hash;
}
// Résultats des vérifications globales
if ($error!="") {
......@@ -106,7 +109,7 @@ class Bank extends Controller
$id = intval($order_id);
$cb_log->write("Id: ".$order_id);
$res = $db->query("SELECT * FROM dons WHERE id='".$id."';");
$don = $res->fetch(PDO::FETCH_ASSOC);
$don = $res->fetch(\PDO::FETCH_ASSOC);
if (!$don) {
$cb_log->write("Transaction id not found: ".$order_id);
......@@ -144,11 +147,11 @@ class Bank extends Controller
// ok, somme OK, status = completed, transaction found.
$db->query("UPDATE dons SET status=status+1 WHERE id='".$id."';");
$res = $db->query("SELECT status FROM dons WHERE id='".$id."';");
$status = $res->fetch(PDO::FETCH_ASSOC);
$status = $res->fetch(\PDO::FETCH_ASSOC);
$status = $status['status'];
$res = $db->query("SELECT * FROM users WHERE id='".$don['user_id']."';");
$user = $res->fetch(PDO::FETCH_ASSOC);
$user = $res->fetch(\PDO::FETCH_ASSOC);
$cb_log->write("Utilisation d'un utilisateur existant");
// Ajout du nouveau don au cumul actuel
if ($status!=101) {
......@@ -156,7 +159,7 @@ class Bank extends Controller
$db->query("UPDATE users set cumul = cumul + " . $don['somme'] . ", total = total + " . $don['somme'] . " WHERE id='".$don['user_id']."'");
}
$result = $db->query("SELECT cumul FROM users WHERE id='".$don['user_id']."'");
$cumul = $result->fetch(PDO::FETCH_ASSOC);
$cumul = $result->fetch(\PDO::FETCH_ASSOC);
$cumul = $cumul['cumul'];
$cb_log->write("Nouveau cumul: ".$cumul);
$user_id = $user['id'];
......@@ -167,7 +170,7 @@ class Bank extends Controller
// Puis envoi du mail
if ($user["email"]) {
$mailer = new SMTP(SMTP_HOST, SMTP_PORT, SMTP_SECURITY, SMTP_USER, SMTP_PW);
$mailer = new \SMTP(SMTP_HOST, SMTP_PORT, SMTP_SECURITY, SMTP_USER, SMTP_PW);
$cb_log->write("Sending email for id: ".$id." at ".$user['email']);
$mailer->set('From', "contact@laquadrature.net");
$mailer->set('FromName', "La Quadrature du Net");
......@@ -195,21 +198,21 @@ Encore merci pour votre soutien,
Toute l'équipe de La Quadrature du Net
")."\n\n";
// Création de l'url d'administration
$admin_url = "https://support.laquadrature.net/perso";
// Création de l'url d'administration
$admin_url = "https://support.laquadrature.net/perso";
foreach ($f3->get('languages') as $key => $language) {
if ($lang==$language[1]) {
$admin_url = "https://".$language[0].$f3->get('dev').".laquadrature.net/perso";
}
}
// Remplacement des variables par leurs valeurs
$fields = array(
"NAME"=>$user["pseudo"],
"SOMME"=>$don['somme'],
"CUMUL"=>$cumul,
"URL_ADMIN" => $admin_url
);
// Remplacement des variables par leurs valeurs
$fields = array(
"NAME"=>$user["pseudo"],
"SOMME"=>$don['somme'],
"CUMUL"=>$cumul,
"URL_ADMIN" => $admin_url
);
foreach ($fields as $k=>$v) {
$text = str_replace("%%".$k."%%", $v, $text);
}
......
<?php
namespace Controller;
class Campaign extends Controller
{
/*
Page principale du site
*/
public function beforeRoute($f3, $args)
{
parent::beforeRoute($f3, $args);
// Valeur par défaut du bloc de contenu
$f3->set('block_content', 'campaign/empty.html');
// Get the database
$f3->set('SESSION.errors', []);
}
public function afterRoute($f3, $args)
{
parent::afterRoute($f3, $args);
// Rendu HTML de la page
echo Template::instance()->render('campaign/base.html');
}
public static function show($f3, $args)
{
// Let's do some math first
// So, get the $db
$db = $f3->get('DB');
// Number of month left for a one-year provisionning since CAMPAIGN_START_DATE
$now = new DateTime('now');
$start = new DateTime(CAMPAIGN_START_DATE);
$now = new \DateTime('now');
$start = new \DateTime(CAMPAIGN_START_DATE);
if ($start > $now) {
$months = 0;
} else {
......@@ -40,7 +23,8 @@ class Campaign extends Controller
$total_provisional = 0;
// So now, let's get the amount of confirmed dons, which are all the 1, 4, 101 and 102
// statuses since CAMPAIGN_START_DATE
$result = $db->query("SELECT SUM(somme)
$result = $db->query(
"SELECT SUM(somme)
AS total_confirmed
FROM dons
WHERE status IN (1, 4, 102)
......@@ -73,26 +57,7 @@ class Campaign extends Controller
// Page d'attente
public function wait($f3)
{
echo Template::instance()->render('campaign/wait.html');
exit;
}
// Que fait la Quadrature ?
public function what($f3)
{
$f3->set('block_content', 'campaign/what.html');
}
// Comment fonctionne la Quadrature ?
public function who($f3)
{
$f3->set('block_content', 'campaign/who.html');
}
// À quoi servent les dons ?
public function why($f3)
{
$f3->set('block_content', 'campaign/why.html');
$f3->set('block_content', 'campaign/wait.html');
}
// FAQ donateurs
......@@ -101,16 +66,6 @@ class Campaign extends Controller
$f3->set('block_content', 'campaign/faq.html');
}
// Matériel de campagne
public function material($f3)
{
// On n'a que des bannières fr et en
if ($f3->get('lang_short')!='fr') {
$f3->set('lang_short', 'en');
}
$f3->set('block_content', 'campaign/material.html');
}
public function merci($f3)
{
$f3->set('form_visible', 'merci');
......@@ -139,29 +94,29 @@ class Campaign extends Controller
// Si l'utilisateur est déjà connecté, on le récupère
if ($f3->get('SESSION.user', true)) {
$user = $db->query("SELECT * FROM users WHERE id = ".$f3->get('SESSION.id'));
$user = $user->fetch(PDO::FETCH_ASSOC);
$user = $user->fetch(\PDO::FETCH_ASSOC);
$email = $user['email'];
$user_id = $user['id'];
$cumul_id = $user['cumul'];
} else {
// Depuis les dons cumulés, on recherche d'abord si le donateur existe déjà (basé sur son email)
$email = Utils::asl($f3->get('email'));
$email = \Utils::asl($f3->get('email'));
$hash = hash('sha256', $f3->get('password'));
$sql = "SELECT id FROM users WHERE email = '".Utils::asl($email)."';";
$sql = "SELECT id FROM users WHERE email = '".\Utils::asl($email)."';";
$result = $db->query($sql);
if ($result->fetchColumn() > 0) {
// We have an existing user, we should try to login with the provided password
// or 403.
$mapper = new DB\SQL\Mapper($f3->get('DB'), 'users');
$mapper = new \DB\SQL\Mapper($f3->get('DB'), 'users');
$auth = new \Auth($mapper, array('id' => 'email', 'pw' => 'hash'));
$login = $auth->login($email, $hash);
if (!$login) {
$f3->error(403);
} else {
$result = $db->query("SELECT id, cumul FROM users WHERE email = '".Utils::asl($email)."'");
$user = $result->fetch(PDO::FETCH_ASSOC);
$result = $db->query("SELECT id, cumul FROM users WHERE email = '".\Utils::asl($email)."'");
$user = $result->fetch(\PDO::FETCH_ASSOC);
$user_id = $user['id'];
$cumul_id = $user['cumul'];
}
......@@ -190,7 +145,7 @@ class Campaign extends Controller
setcookie("donlqdn", md5("SALT!!!".$id."!!!"), 86400, "/");
$target = PAYMENT_URL; //"https://paiement.systempay.fr/vads-payment/";
$transaction_date = new DateTime('now', new DateTimeZone("UTC"));
$transaction_date = new \DateTime('now', new \DateTimeZone("UTC"));
$params = array(
// Champs obligatoires
"vads_trans_date" => $transaction_date->format("YmdHis"),
......@@ -241,7 +196,7 @@ class Campaign extends Controller
$signature .= $value."+";
}
$signature .= CERTIFICATE;
$signature = sha1($signature);
$signature = base64_encode(hash_hmac('sha256', $signature, CERTIFICATE, true));
$params["signature"] = $signature;
$f3->set('target', $target);
......@@ -249,7 +204,7 @@ class Campaign extends Controller
$f3->set('signature', $signature);
// Log des informations envoyées pour debug en cas de souci
$don_log = new Log('dons.log');
$don_log = new \Log('dons.log');
$don_log->write('target : '. $target);
$don_log->write('params : ');
foreach ($params as $key=>$value) {
......
<?php
namespace Controller;
class Controller
{
// Constructeur
public function __construct()
{
$f3=Base::instance();
$f3=\Base::instance();
}
// Fonction appelée avant routage
public function beforeRoute($f3, $args)
{
// Template de base par défaut
$this->template = "base.html";
if (php_sapi_name() == 'cli') {
$HTTP_HOST = 'localhost';
} else {
......@@ -50,16 +54,17 @@ class Controller
$f3->set('lang_short', $lang_short);
// Initialize DB
$f3->set('DB', new \DB\SQL(SQL_DSN, SQL_USER, SQL_PASSWORD,
array( \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION)));
$f3->set('DB', new \DB\SQL(
SQL_DSN,
SQL_USER,
SQL_PASSWORD,
array( \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION)
));
if ($f3->get('VERB') == 'POST') {
$this->sanitizeForms($f3);
}
// Log dir
$f3->set('LOGS', LOGS);
$f3->set('mail', array(
"from" => "contact@laquadrature.net",
"fromName" => "La Quadrature du Net"
......@@ -78,11 +83,27 @@ class Controller
);
asort($atailles);
$f3->set('atailles', $atailles);
// Ceci n'est valable que pour les tests en CLI
if (strlen($f3["RESPONSE"])>0) {
$f3->clear("RESPONSE");
return;
}
}
// Fonction appelée après routage
public function afterRoute($f3, $args)
{
// Ceci n'est valable que pour les tests en CLI
if (strlen($f3["RESPONSE"])>0) {
echo $f3["RESPONSE"];
return;
}
// Rendu de la page
if ($this->template!='') {
echo \Template::instance()->render($this->template);
}
}
// Fonction utilisée pour sanitiser les données
......@@ -95,7 +116,7 @@ class Controller
// CSRF checks
//if ($f3->get('POST.csrf') != $f3->get('container')['session']->get('csrf')) {
// $f3->error('400', 'CSRF Validation error');
// $f3->error('400', 'CSRF Validation error');
//}
// First, let's clean all the data
......
......@@ -5,6 +5,7 @@
* The command should be called like this :
* php index.php "/action"
*/
namespace Controller;
class Cron extends Controller
{
......@@ -21,26 +22,26 @@ class Cron extends Controller
{
// This method is used to generate a piplome. It might be called with an
// id parameters, in which case we will ask to regenerate a specific piplome.
$logger = new Log("/piplome.log");
$logger = new \Log("/piplome.log");
$ids = array();
$db = $f3->get('DB');
if (array_key_exists('id', $args)) {
// Let's check if we can have a pdf
$result = $db->query("SELECT dons.id as id, decimale, users.pseudo as nom, lang FROM dons LEFT JOIN users ON dons.user_id = users.id WHERE dons.status IN (1, 4, 101) AND dons.id='".Utils::asl($args['id'])."'");
$result = $db->query("SELECT dons.id as id, decimale, users.pseudo as nom, lang FROM dons LEFT JOIN users ON dons.user_id = users.id WHERE dons.status IN (1, 4, 101) AND dons.id='".\Utils::asl($args['id'])."'");
} else {
// We want to generate all piplomes which does not exists yet
$result = $db->query("SELECT dons.id as id, decimale, users.pseudo as nom, lang FROM dons LEFT JOIN users ON dons.user_id = users.id WHERE pdf='' AND dons.status IN (1, 4, 101) LIMIT 100;");
}
$pdfs = [];
foreach ($result->fetchAll(PDO::FETCH_ASSOC) as $row) {
foreach ($result->fetchAll(\PDO::FETCH_ASSOC) as $row) {
$pdfs[] = $row;
}
// We do have our IDs
// We need to create a PDF
$lock = @fopen("tmp/piplomes.lock", "ab");
$lock = @fopen("../tmp/piplomes.lock", "ab");
if ($lock) {
flock($lock, LOCK_EX);
foreach ($pdfs as $pdf) {
......@@ -50,8 +51,8 @@ class Cron extends Controller
do {
$decimale = rand(10, 200000);
$logger->write("Trying if $decimale is free");
$result = $db->query("SELECT id FROM dons WHERE decimale='".Utils::asl($decimale)."';");
$tmp = $result->fetchAll(PDO::FETCH_COLUMN, 'id');
$result = $db->query("SELECT id FROM dons WHERE decimale='".\Utils::asl($decimale)."';");
$tmp = $result->fetchAll(\PDO::FETCH_COLUMN, 'id');
} while ($tmp);
$logger->write("Found a decimal of pi: ".$decimale."");
$pdf['decimale'] = $decimale;
......@@ -63,9 +64,9 @@ class Cron extends Controller
// Let's trash the garbage
foreach (array('tex', 'aux', 'pdf', 'log') as $ext) {
@unlink(dirname(__FILE__)."/../tmp/pplome.".$ext);
@unlink(dirname(__FILE__)."/../../tmp/pplome.".$ext);
}
// We now have only fr or en templates
if ($pdf['lang'] == 'fr') {
$pdf['lang'] = 'fr_FR';
......@@ -75,10 +76,11 @@ class Cron extends Controller
}
$logger->write("Language is ".$pdf['lang']."");
$template = file_get_contents(dirname(__FILE__)."/../locales/".$pdf['lang']."/LC_MESSAGES/plome.tex");
$template = file_get_contents(dirname(__FILE__)."/../../locales/".$pdf['lang']."/LC_MESSAGES/plome.tex");
// We will open the pi-decimals file
$pi = fopen(dirname(__FILE__)</