diff options
-rw-r--r-- | app/class/Application.php | 32 | ||||
-rw-r--r-- | app/class/Config.php | 116 | ||||
-rw-r--r-- | app/class/Controller.php | 2 | ||||
-rw-r--r-- | app/class/Controllerconnect.php | 66 | ||||
-rw-r--r-- | app/class/Modelauthtoken.php | 61 | ||||
-rw-r--r-- | app/class/Modeluser.php | 42 | ||||
-rw-r--r-- | app/fn/fn.php | 13 | ||||
-rw-r--r-- | app/view/templates/backtopbar.php | 3 | ||||
-rw-r--r-- | app/view/templates/connect.php | 3 |
9 files changed, 251 insertions, 87 deletions
diff --git a/app/class/Application.php b/app/class/Application.php index 70c899f..4ddf37e 100644 --- a/app/class/Application.php +++ b/app/class/Application.php @@ -41,7 +41,7 @@ class Application } else { if(Config::readconfig()) { - if(!Config::checkbasepath() || empty(Config::pagetable()) || !is_dir(Model::RENDER_DIR) || !Config::checkdomain()) { + if(!Config::checkbasepath() || empty(Config::pagetable()) || !is_dir(Model::RENDER_DIR) || !Config::checkdomain() || empty(Config::secretkey())) { echo '<ul>'; if(!Config::checkbasepath()) { echo '<li>Wrong path</li>'; @@ -55,6 +55,9 @@ class Application if(!is_dir(Model::RENDER_DIR)) { echo '<li>Render path not existing</li>'; } + if(!is_dir(Model::RENDER_DIR)) { + echo '<li>Secret Key not set or not valid</li>'; + } echo '</ul>'; $this->configform(); exit; @@ -84,18 +87,25 @@ class Application <form action="" method="post"> <div> - <h2> - <label for="basepath">Path to W-CMS</label> - </h2> - <input type="text" name="configinit[basepath]" value="<?= Config::basepath() ?>" id="basepath"> - <p><i>Leave it empty if W-CMS is in your root folder, otherwise, indicate the subfolder(s) in witch you installed the CMS</i></p> + <h2> + <label for="basepath">Path to W-CMS</label> + </h2> + <input type="text" name="configinit[basepath]" value="<?= Config::basepath() ?>" id="basepath"> + <p><i>Leave it empty if W-CMS is in your root folder, otherwise, indicate the subfolder(s) in witch you installed the CMS</i></p> </div> <div> - <h2> - <label for="pagetable">Name of your database table</label> - </h2> - <input type="text" name="configinit[pagetable]" value="<?= Config::pagetable() ?>" id="pagetable"> - <p><i>Set the name of the first folder that is going to store all your work</i></p> + <h2> + <label for="pagetable">Name of your database table</label> + </h2> + <input type="text" name="configinit[pagetable]" value="<?= Config::pagetable() ?>" id="pagetable"> + <p><i>Set the name of the first folder that is going to store all your work</i></p> + </div> + <div> + <h2> + <label for="secretkey">Secret Key</label> + </h2> + <input type="text" name="configinit[secretkey]" value="<?= bin2hex(random_bytes(10)) ?>" id="secretkey" minlength="16" maxlength="128" required> + <p><i>The secret key is used to secure cookies. There are no need to remind it. (16 to 128 characters)</i></p> </div> <input type="submit" value="set"> </form> diff --git a/app/class/Config.php b/app/class/Config.php index 2cfdcd3..4736410 100644 --- a/app/class/Config.php +++ b/app/class/Config.php @@ -11,7 +11,7 @@ abstract class Config protected static $domain = ''; protected static $fontsize = 15; protected static $basepath = ''; - protected static $route404; + protected static $route404; protected static $alerttitle = ''; protected static $alertlink = ''; protected static $alertlinktext = ''; @@ -22,10 +22,10 @@ abstract class Config protected static $privatepass = false; protected static $notpublishedpass = false; protected static $alertcss = false; - protected static $defaultbody = '%HEADER%'. PHP_EOL .PHP_EOL . '%NAV%'. PHP_EOL .PHP_EOL . '%ASIDE%'. PHP_EOL .PHP_EOL . '%MAIN%'. PHP_EOL .PHP_EOL . '%FOOTER%'; + protected static $defaultbody = '%HEADER%' . PHP_EOL . PHP_EOL . '%NAV%' . PHP_EOL . PHP_EOL . '%ASIDE%' . PHP_EOL . PHP_EOL . '%MAIN%' . PHP_EOL . PHP_EOL . '%FOOTER%'; protected static $defaultfavicon = ''; protected static $defaultthumbnail = ''; - protected static $analytics = ''; + protected static $analytics = ''; protected static $externallinkblank = true; protected static $internallinkblank = false; protected static $reccursiverender = true; @@ -34,10 +34,14 @@ abstract class Config protected static $homeredirect = null; protected static $interfacecss = null; protected static $bookmark = []; + protected static $secretkey = null; protected static $sentrydsn = ''; + const SECRET_KEY_MIN = 16; + const SECRET_KEY_MAX = 128; -// _______________________________________ F U N _______________________________________ + + // _______________________________________ F U N _______________________________________ @@ -92,9 +96,9 @@ abstract class Config /** * Calculate Domain name */ - public static function getdomain() - { - self::$domain = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST']; + public static function getdomain() + { + self::$domain = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST']; } /** @@ -109,12 +113,12 @@ abstract class Config * Generate full url adress where W is installed * @return string url adress finished by a slash "/" */ - public static function url($endslash = true) : string + public static function url($endslash = true): string { return self::$domain . (!empty(self::$basepath) ? '/' . self::$basepath : "") . ($endslash ? '/' : ''); } -// ________________________________________ G E T _______________________________________ + // ________________________________________ G E T _______________________________________ public static function pagetable() { @@ -134,9 +138,9 @@ abstract class Config /** * @param bool $trailingslash If not empty basepath, add a trailing slash after the basepath */ - public static function basepath(bool $trailingslash = false) : string + public static function basepath(bool $trailingslash = false): string { - if($trailingslash && !empty(self::$basepath)) { + if ($trailingslash && !empty(self::$basepath)) { return self::$basepath . '/'; } else { return self::$basepath; @@ -187,12 +191,12 @@ abstract class Config { return self::$privatepass; } - + public static function notpublishedpass() { return self::$notpublishedpass; } - + public static function alertcss() { return self::$alertcss; @@ -258,13 +262,18 @@ abstract class Config return self::$bookmark; } + public static function secretkey() + { + return self::$secretkey; + } + public static function sentrydsn() { return self::$sentrydsn; } -// __________________________________________ S E T ______________________________________ + // __________________________________________ S E T ______________________________________ public static function setpagetable($pagetable) { @@ -291,68 +300,68 @@ abstract class Config public static function setroute404($id) { - if(is_string($id)) { + if (is_string($id)) { self::$route404 = idclean($id); } } public static function setalerttitle($alerttitle) { - if(is_string($alerttitle)) { + if (is_string($alerttitle)) { self::$alerttitle = strip_tags($alerttitle); } } public static function setalertlink($alertlink) { - if(is_string($alertlink)) { + if (is_string($alertlink)) { self::$alertlink = idclean(strip_tags($alertlink)); } } public static function setalertlinktext($alertlinktext) { - if(is_string($alertlinktext)) { + if (is_string($alertlinktext)) { self::$alertlinktext = strip_tags($alertlinktext); } } public static function setexistnot($existnot) { - if(is_string($existnot)) { + if (is_string($existnot)) { self::$existnot = strip_tags($existnot); } } public static function setprivate($private) { - if(is_string($private)) { + if (is_string($private)) { self::$private = strip_tags($private); } } public static function setnotpublished($notpublished) { - if(is_string($notpublished)) { + if (is_string($notpublished)) { self::$notpublished = strip_tags($notpublished); } } - + public static function setexistnotpass($existnotpass) { self::$existnotpass = boolval($existnotpass); } - + public static function setprivatepass($privatepass) { self::$privatepass = boolval($privatepass); } - + public static function setnotpublishedpass($notpublishedpass) { self::$notpublishedpass = boolval($notpublishedpass); } - + public static function setalertcss($alertcss) { self::$alertcss = boolval($alertcss); @@ -360,32 +369,32 @@ abstract class Config public static function setdefaultbody($defaultbody) { - if(is_string($defaultbody)) { + if (is_string($defaultbody)) { self::$defaultbody = $defaultbody; } } public static function setdefaultfavicon($defaultfavicon) { - if(is_string($defaultfavicon)) { + if (is_string($defaultfavicon)) { self::$defaultfavicon = $defaultfavicon; } } public static function setdefaultthumbnail($defaultthumbnail) { - if(is_string($defaultthumbnail)) { + if (is_string($defaultthumbnail)) { self::$defaultthumbnail = $defaultthumbnail; } } public static function setanalytics($analytics) { - if(is_string($analytics) && strlen($analytics) < 25) { + if (is_string($analytics) && strlen($analytics) < 25) { self::$analytics = $analytics; } } - + public static function setexternallinkblank($externallinkblank) { self::$externallinkblank = boolval($externallinkblank); @@ -404,21 +413,21 @@ abstract class Config public static function setdefaultprivacy($defaultprivacy) { $defaultprivacy = intval($defaultprivacy); - if($defaultprivacy >= 0 && $defaultprivacy <= 2) { + if ($defaultprivacy >= 0 && $defaultprivacy <= 2) { self::$defaultprivacy = $defaultprivacy; } } public static function sethomepage($homepage) { - if(in_array($homepage, Model::HOMEPAGE)) { + if (in_array($homepage, Model::HOMEPAGE)) { self::$homepage = $homepage; } } public static function sethomeredirect($homeredirect) { - if(is_string($homeredirect) && strlen($homeredirect) > 0) { + if (is_string($homeredirect) && strlen($homeredirect) > 0) { self::$homeredirect = idclean($homeredirect); } else { self::$homeredirect = null; @@ -427,7 +436,7 @@ abstract class Config public static function setinterfacecss($interfacecss) { - if(is_string($interfacecss) && file_exists(Model::CSS_DIR . $interfacecss)) { + if (is_string($interfacecss) && file_exists(Model::CSS_DIR . $interfacecss)) { self::$interfacecss = $interfacecss; } else { self::$interfacecss = null; @@ -436,11 +445,24 @@ abstract class Config public static function setbookmark($bookmark) { - if(is_array($bookmark)) { + if (is_array($bookmark)) { self::$bookmark = $bookmark; } } + public static function setsecretkey($secretkey) + { + if (is_string($secretkey)) { + $stripedsecretkey = strip_tags($secretkey); + if ($stripedsecretkey === $secretkey) { + $length = strlen($secretkey); + if ($length < self::SECRET_KEY_MAX && $length > self::SECRET_KEY_MIN) { + self::$secretkey = $secretkey; + } + } + } + } + public static function setsentrydsn($sentrydsn) { if (is_string($sentrydsn)) { @@ -457,31 +479,17 @@ abstract class Config public static function addbookmark(string $id, string $query) { - if(!empty($id) && !empty($query)) { - $id = idclean($id); - $id = substr($id, 0, 16); - self::$bookmark[$id] = $query; + if (!empty($id) && !empty($query)) { + $id = idclean($id); + $id = substr($id, 0, 16); + self::$bookmark[$id] = $query; } } public static function deletebookmark(string $id) { - if(key_exists($id, self::$bookmark)) { + if (key_exists($id, self::$bookmark)) { unset(self::$bookmark[$id]); } } - - - - } - - - - - - - - - -?>
\ No newline at end of file diff --git a/app/class/Controller.php b/app/class/Controller.php index c3787b2..7398a7d 100644 --- a/app/class/Controller.php +++ b/app/class/Controller.php @@ -34,7 +34,7 @@ class Controller public function setuser() { - $this->usermanager = new Modeluser; + $this->usermanager = new Modeluser; $this->user = $this->usermanager->readsession(); } diff --git a/app/class/Controllerconnect.php b/app/class/Controllerconnect.php index 592c0ee..e9af86a 100644 --- a/app/class/Controllerconnect.php +++ b/app/class/Controllerconnect.php @@ -21,7 +21,7 @@ class Controllerconnect extends Controller public function connect() { - if(isset($_SESSION['pageupdate'])) { + if (isset($_SESSION['pageupdate'])) { $pageupdate['route'] = 'pageedit'; $pageupdate['id'] = $_SESSION['pageupdate']['id']; } else { @@ -38,14 +38,22 @@ class Controllerconnect extends Controller { if (isset($_POST['pass'])) { $this->user = $this->usermanager->passwordcheck($_POST['pass']); - if($this->user != false) { - if($this->user->expiredate() === false || $this->user->level() === 10 || $this->user->expiredate('date') > $this->now) { + if ($this->user != false) { + if ($this->user->expiredate() === false || $this->user->level() === 10 || $this->user->expiredate('date') > $this->now) { $this->user->connectcounter(); $this->usermanager->add($this->user); $this->usermanager->writesession($this->user); $_SESSION['workspace']['showleftpanel'] = true; $_SESSION['workspace']['showrightpanel'] = false; - } + + if ($_POST['rememberme'] && $this->user->cookie() > 0) { + $token = $this->createauthtoken(); + if ($token) { + $_SESSION['user' . Config::basepath()]['authtoken'] = $token; + } + } + + } } } if ($id !== null) { @@ -59,6 +67,9 @@ class Controllerconnect extends Controller { $this->user = $this->usermanager->logout(); $this->usermanager->writesession($this->user); + if(!empty($_SESSION['user' . Config::basepath()]['authtoken'])) { + $this->destroyauthtoken($_SESSION['user' . Config::basepath()]['authtoken']); + } if ($id !== null && $route !== 'home') { $this->routedirect($route, ['page' => $id]); } else { @@ -66,13 +77,50 @@ class Controllerconnect extends Controller } } + /** + * Create a token stored in the database and then a cookie + * + * @return string|bool Token in cas of success, otherwise, false. + */ + public function createauthtoken() + { + $authtoken = new Modelauthtoken(); + $tokenid = $authtoken->add($this->user); + if ($tokenid !== false) { + $cookiecreation = $this->creatauthcookie($tokenid, $this->user->cookie()); + if ($cookiecreation) { + return $tokenid; + } + } else { + return false; + } + } -} - - - + /** + * Create a cookie called `authtoken` + * + * @param string $token Token string + * @param int $conservation Time in day to keep the token + * + * @return bool True in cas of success, otherwise, false. + */ + public function creatauthcookie(string $token, int $conservation): bool + { + $hash = secrethash($token); + $cookie = $token . ':' . $hash; + return setcookie('authtoken', $cookie, time() + $conservation * 24 * 3600, null, null, false, true); + } + /** + * Destroy the current token + */ + public function destroyauthtoken(string $id) + { + $authtoken = new Modelauthtoken(); + $dbdelete = $authtoken->delete($id); + //deleteauthcookie + } -?>
\ No newline at end of file +} diff --git a/app/class/Modelauthtoken.php b/app/class/Modelauthtoken.php new file mode 100644 index 0000000..18ef6a7 --- /dev/null +++ b/app/class/Modelauthtoken.php @@ -0,0 +1,61 @@ +<?php + +namespace Wcms; + +use JamesMoss\Flywheel\Document; + +class Modelauthtoken extends Modeldb +{ + + const AUTHTOKEN_REPO_NAME = 'authtoken'; + const AUTHTOKEN_ID_LENGTH = 30; + + public function __construct() + { + parent::__construct(); + $this->storeinit(self::AUTHTOKEN_REPO_NAME); + } + + /** + * Add a Token in the database according to the Users datas + * + * @param User $user + */ + public function add(User $user) + { + $datas = [ + 'user' => $user->id(), + 'ip' => $_SERVER['SERVER_ADDR'], + 'creationdate' => '1' + ]; + $tokendata = new Document($datas); + + $exist = true; + while ($exist !== false) { + $id = bin2hex(random_bytes(self::AUTHTOKEN_ID_LENGTH)); + $exist = $this->repo->findById($id); + } + + $tokendata->setId($id); + return $this->repo->store($tokendata); + + } + + public function getbytoken(string $token) + { + return $this->repo->findById($token); + } + + public function delete(string $token) + { + return $this->repo->delete($token); + } + +} + + + + + + +?>
\ No newline at end of file diff --git a/app/class/Modeluser.php b/app/class/Modeluser.php index 071320e..9ee04ba 100644 --- a/app/class/Modeluser.php +++ b/app/class/Modeluser.php @@ -21,16 +21,16 @@ class Modeluser extends Modeldb $this->storeinit(self::USER_REPO_NAME); } + /** + * Write session cookie according to users datas and define the current authtoken being used + * + * @param User $user Current user to keep in session + */ public function writesession(User $user) { - $_SESSION['user' . Config::basepath()] = ['level' => $user->level(), 'id' => $user->id(), 'columns' =>$user->columns()]; - } - - public function writecookie(User $user) - { - $cookiehash = - $cookie = ['level' => $user->level(), 'id' => $user->id()]; - setcookie('user ' . Config::basepath(), $cookie, time() + $user->cookie()*24*3600, null, null, false, true); + $_SESSION['user' . Config::basepath()]['level'] = $user->level(); + $_SESSION['user' . Config::basepath()]['id'] = $user->id(); + $_SESSION['user' . Config::basepath()]['columns'] = $user->columns(); } public function readsession() @@ -41,9 +41,27 @@ class Modeluser extends Modeldb $user = new User($userdatas); $user = $this->get($user); return $user; - } else { - return new User(['id' => '', 'level' => 0]); } + + if(isset($_COOKIE['authtoken']) && strpos($_COOKIE['authtoken'], ':')) { + list($cookietoken, $cookiemac) = explode(':', $_COOKIE['authtoken']); + $authtokenmanager = new Modelauthtoken(); + $dbtoken = $authtokenmanager->getbytoken($cookietoken); + + if ($dbtoken !== false) { + if(hash_equals($cookiemac, secrethash($dbtoken->getId()))) { + $user = $this->get($dbtoken->user); + if ($user !== false) { + $this->writesession($user, $_COOKIE['authtoken']); + } + return $user; + } + + } + } + + return new User(['id' => '', 'level' => 0]); + } @@ -56,7 +74,7 @@ class Modeluser extends Modeldb /** - * @return array list of User objects + * @return User[] associative array of User objects `id => User` */ public function getlister() { @@ -159,7 +177,7 @@ class Modeluser extends Modeldb /** - * @param string|User $id + * @param string|User $id Can be an User object or a string ID * * @return User|false User object or false in case of error */ diff --git a/app/fn/fn.php b/app/fn/fn.php index 3ca31c9..01e643b 100644 --- a/app/fn/fn.php +++ b/app/fn/fn.php @@ -324,6 +324,19 @@ function options(array $options, $selected = null) : string } +/** + * Hash a Token using secret key and sha256 + * + * @param string $token Input token + * + * @return string Hashed mac + */ +function secrethash(string $token) : string +{ + return hash_hmac('sha256', $token, Wcms\Config::secretkey()); +} + + diff --git a/app/view/templates/backtopbar.php b/app/view/templates/backtopbar.php index c1dd361..0710c85 100644 --- a/app/view/templates/backtopbar.php +++ b/app/view/templates/backtopbar.php @@ -63,6 +63,9 @@ if($user->isadmin()) { <form action="<?= $this->url('log') ?>" method="post" id="connect"> <input type="password" name="pass" id="loginpass" placeholder="password" autofocus> <input type="hidden" name="route" value="home"> +<input type="hidden" name="rememberme" value="0"> +<input type="checkbox" name="rememberme" id="rememberme" value="1"> +<label for="rememberme">Remember me</label> <input type="submit" name="log" value="login"> </form> diff --git a/app/view/templates/connect.php b/app/view/templates/connect.php index e21b360..6fd5b14 100644 --- a/app/view/templates/connect.php +++ b/app/view/templates/connect.php @@ -19,6 +19,9 @@ if(in_array($route, ['pageedit', 'pageread', 'pageread/', 'pageadd'])) { } ?> <input type="password" name="pass" id="loginpass" placeholder="password" autofocus> +<input type="hidden" name="rememberme" value="0"> +<input type="checkbox" name="rememberme" id="rememberme" value="1"> +<label for="rememberme">Remember me</label> <input name="log" type="submit" value="login"> </form> |