diff --git a/app/class/Config.php b/app/class/Config.php index 08ddaec..ce71abb 100644 --- a/app/class/Config.php +++ b/app/class/Config.php @@ -59,6 +59,12 @@ abstract class Config /** Indicate if img should have loading="lazy" attribute */ protected static bool $lazyloadimg = true; + /** LDAP auth */ + protected static string $ldapserver = ''; + protected static string $ldaptree = ''; + protected static string $ldapu = ''; + protected static int $ldapuserlevel = 0; + public const LANG_MIN = 2; public const LANG_MAX = 16; @@ -156,6 +162,18 @@ public static function url($endslash = true): string return self::$domain . (!empty(self::$basepath) ? '/' . self::$basepath : "") . ($endslash ? '/' : ''); } + /** + * @return bool Indicate if ldap is configured. (all 3 params are not empty) + */ + public static function isldap(): bool + { + return ( + !empty(self::$ldapserver) + && !empty(self::$ldaptree) + && !empty(self::$ldapu) + ); + } + // ________________________________________ G E T _______________________________________ public static function pagetable() @@ -349,6 +367,26 @@ public static function lazyloadimg(): bool return self::$lazyloadimg; } + public static function ldapserver(): string + { + return self::$ldapserver; + } + + public static function ldaptree(): string + { + return self::$ldaptree; + } + + public static function ldapu(): string + { + return self::$ldapu; + } + + public static function ldapuserlevel(): int + { + return self::$ldapuserlevel; + } + // __________________________________________ S E T ______________________________________ @@ -592,4 +630,26 @@ public static function setlazyloadimg($lazyloadimg): bool { return self::$lazyloadimg = boolval($lazyloadimg); } + + public static function setldapserver(string $ldapserver): void + { + self::$ldapserver = $ldapserver; + } + + public static function setldaptree(string $ldaptree): void + { + self::$ldaptree = $ldaptree; + } + + public static function setldapu(string $ldapu): void + { + self::$ldapu = $ldapu; + } + + public static function setldapuserlevel(int $ldapuserlevel): void + { + if ($ldapuserlevel >= 0 && $ldapuserlevel <= 10) { + self::$ldapuserlevel = $ldapuserlevel; + } + } } diff --git a/app/class/Controllerconnect.php b/app/class/Controllerconnect.php index f2bed79..9713234 100644 --- a/app/class/Controllerconnect.php +++ b/app/class/Controllerconnect.php @@ -3,7 +3,6 @@ namespace Wcms; use RuntimeException; -use Wcms\Exception\Database\Notfoundexception; class Controllerconnect extends Controller { @@ -16,9 +15,14 @@ public function log(): void $id = $_POST['id'] ?? null; $route = $_POST['route'] ?? 'home'; if ($_POST['log'] === 'login') { - $this->login($route, $id); + $this->login(); } elseif ($_POST['log'] === 'logout') { - $this->logout($route, $id); + $this->logout(); + } + if (is_string($id)) { + $this->routedirect($route, ['page' => $id]); + } else { + $this->routedirect($route); } } } @@ -39,74 +43,115 @@ public function connect(): void /** - * Will login an user using POST datas and redirect - * - * @param string $route For redirection - * @param ?string $paramid For redirection (optionnal, can be used for pages redirection) + * Will try to login an user using POST datas */ - protected function login(string $route, ?string $paramid = null): void + protected function login(): void { if (!empty($_POST['pass']) && !empty($_POST['user'])) { + $this->modelconnect = new Modelconnect(); $userid = $_POST['user']; + $pass = false; + try { $this->user = $this->usermanager->get($userid); // May throw DatabaseException - if (!$this->usermanager->passwordcheck($this->user, $_POST['pass'])) { - $userid = $this->user->id(); - $this->sendflashmessage("Wrong credentials", self::FLASH_ERROR); - Logger::error("wrong credential for user : '$userid' when attempting to loggin"); - } elseif ( - $this->user->expiredate() !== false && - $this->user->expiredate('date') < $this->now && - $this->user->level() < 10 - ) { - $this->sendflashmessage("Account expired", self::FLASH_ERROR); + $userisindb = true; + } catch (RuntimeException $e) { + $userisindb = false; + if (Config::ldapuserlevel() > 0) { + $this->user = new User(['password' => null, 'level' => Config::ldapuserlevel(), 'id' => $userid]); } else { - $this->user->connectcounter(); - $this->usermanager->add($this->user); - $this->servicesession->setuser($this->user->id()); - $this->sendflashmessage("Successfully logged in as " . $this->user->id(), self::FLASH_SUCCESS); - - if (!empty($_POST['rememberme'])) { - if ($this->user->cookie() > 0) { - $this->modelconnect = new Modelconnect(); - $wsessionid = $this->user->newsession(); - $this->modelconnect->createauthcookie( - $this->user->id(), - $wsessionid, - $this->user->cookie() - ); - $this->usermanager->add($this->user); - $this->servicesession->setwsessionid($wsessionid); - } else { - $message = "Can't remember you beccause user cookie conservation time is set to 0 days"; - $this->sendflashmessage($message, self::FLASH_WARNING); - } - } + $this->sendflashmessage('Wrong credentials', self::FLASH_ERROR); + Logger::errorex($e); + return; + } + } + + if ($this->user->isldap()) { + if (!Config::isldap()) { + $this->sendflashmessage('Error with LDAP connection', self::FLASH_ERROR); + Logger::error("User $userid tried to authenticate against LDAP, but LDAP is not configured"); + return; + } + try { + $ldap = new Modelldap(Config::ldapserver(), Config::ldaptree(), Config::ldapu()); + $pass = $ldap->auth($userid, $_POST['pass']); + $ldap->disconnect(); + } catch (RuntimeException $e) { + $this->sendflashmessage('Error with LDAP connection', self::FLASH_ERROR); + Logger::errorex($e); + return; } - } catch (Notfoundexception $e) { + } else { + $pass = $this->usermanager->passwordcheck($this->user, $_POST['pass']); + } + + if (!$pass) { $this->sendflashmessage("Wrong credentials", self::FLASH_ERROR); - Logger::errorex($e); + return; + } + + if ( + $this->user->expiredate() !== false && + $this->user->expiredate('date') < $this->now && + $this->user->level() < 10 + ) { + $this->sendflashmessage("Account expired", self::FLASH_ERROR); + return; + } + + try { + $this->user->connectcounter(); + $this->usermanager->add($this->user); + $this->servicesession->setuser($this->user->id()); + $this->sendflashmessage("Successfully logged in as " . $this->user->id(), self::FLASH_SUCCESS); } catch (RuntimeException $e) { - $message = "Can't create authentification cookie : $e"; - $this->sendflashmessage($message, self::FLASH_WARNING); - Logger::error($message); + $this->sendflashmessage('Error while trying to persist user in database', self::FLASH_ERROR); + Logger::errorex($e); + if (! $userisindb) { // if user was'nt in database, this mean creation failed. In this case, abort. + return; + } + } + + if (!empty($_POST['rememberme'])) { + try { + if ($this->user->cookie() > 0) { + $wsessionid = $this->user->newsession(); + $this->modelconnect->createauthcookie( + $this->user->id(), + $wsessionid, + $this->user->cookie() + ); + $this->usermanager->add($this->user); + $this->servicesession->setwsessionid($wsessionid); + } else { + $message = "Can't remember you beccause user cookie conservation time is set to 0 days"; + $this->sendflashmessage($message, self::FLASH_WARNING); + } + } catch (RuntimeException $e) { + $message = "Can't create authentification cookie : $e"; + $this->sendflashmessage($message, self::FLASH_WARNING); + Logger::error($message); + } + } + + // if user was not in database before, we can creat it's author personnal bookmark + if (!$userisindb) { + try { + $bookmarkmanager = new Modelbookmark(); + $bookmarkmanager->addauthorbookmark($this->user); + } catch (RuntimeException $e) { + $this->sendflashmessage( + 'error while creating user\'s personnal author bookmark', + self::FLASH_WARNING + ); + Logger::errorex($e); + } } - } - if (is_string($paramid)) { - $this->routedirect($route, ['page' => $paramid]); - } else { - $this->routedirect($route); } } - public function logout($route, $id = null): void + protected function logout(): void { $this->disconnect(); - - if ($id !== null && $route !== 'home') { - $this->routedirect($route, ['page' => $id]); - } else { - $this->routedirect($route); - } } } diff --git a/app/class/Controllerprofile.php b/app/class/Controllerprofile.php index cca48b0..3238bcf 100644 --- a/app/class/Controllerprofile.php +++ b/app/class/Controllerprofile.php @@ -53,6 +53,12 @@ public function update() */ public function password() { + if ($this->user->isldap()) { + http_response_code(403); + $this->showtemplate('forbidden', ['route' => 'profile']); + exit; + } + if ( !isset($_POST['currentpassword']) || !$this->usermanager->passwordcheck($this->user, $_POST['currentpassword']) diff --git a/app/class/Controlleruser.php b/app/class/Controlleruser.php index 98090a4..7965a47 100644 --- a/app/class/Controlleruser.php +++ b/app/class/Controlleruser.php @@ -50,8 +50,18 @@ public function add() $this->sendflashmessage('User successfully added', self::FLASH_SUCCESS); } catch (Databaseexception $e) { $this->sendflashmessage($e->getMessage(), self::FLASH_ERROR); + Logger::errorex($e); + } + try { + $bookmarkmanager = new Modelbookmark(); + $bookmarkmanager->addauthorbookmark($user); + } catch (RuntimeException $e) { + $this->sendflashmessage( + 'error while creating user\'s personnal author bookmark', + self::FLASH_WARNING + ); + Logger::errorex($e); } - $this->addauthorbookmark($user); $this->routedirect('user'); } } @@ -107,6 +117,8 @@ protected function delete(array $datas): void * @throws Notfoundexception If User is not found in the database * @throws Databaseexception If an error occured with database * @throws Runtimeexception In case of other various problems + * + * @todo move this to Modeluser */ protected function update(array $datas): void { @@ -127,43 +139,18 @@ protected function update(array $datas): void && $this->user->id() === $user->id() ) { throw new RuntimeException('You cannot quit administration job'); - } else { - if ($userupdate->password() !== $user->password() && $user->passwordhashed()) { - $userupdate->setpasswordhashed(false); - } - if ($userupdate->passwordhashed() && !$user->passwordhashed()) { - $userupdate->hashpassword(); - } - $this->usermanager->update($userupdate); } - } - /** - * Create a bookmark that filter pages where the user is an author. - * Send a flash message in case of error. - * - * @param User $user The concerned user (need to be already added in database) - */ - protected function addauthorbookmark(User $user): void - { - try { - $bookmarkmanager = new Modelbookmark(); - $userbookmark = new Bookmark(); - $uid = $user->id(); - $userbookmark->init( - "$uid-is-author", - "?authorfilter[0]=$uid&submit=filter", - '👤', - "$uid's pages", - "Pages where $uid is listed as an author", - ); - $userbookmark->setuser($user->id()); - $bookmarkmanager->add($userbookmark); - } catch (RuntimeException $e) { - $this->sendflashmessage( - 'Could not create personnal user author bookmark: ' . $e->getMessage(), - self::FLASH_ERROR - ); + if ($userupdate->password() !== $user->password() && $user->passwordhashed()) { + $userupdate->setpasswordhashed(false); + } + if ($userupdate->passwordhashed() && !$user->passwordhashed()) { + $userupdate->hashpassword(); + } + if ($user->isldap()) { + $userupdate->removepassword(); + $userupdate->setpasswordhashed(false); } + $this->usermanager->update($userupdate); } } diff --git a/app/class/Modelbookmark.php b/app/class/Modelbookmark.php index 9694645..3975316 100644 --- a/app/class/Modelbookmark.php +++ b/app/class/Modelbookmark.php @@ -192,6 +192,28 @@ public function update(Bookmark $bookmark): Bookmark return $bookmark; } + /** + * Create a bookmark that filter pages where the given user is an author. + * + * @param User $user The concerned user (need to be already added in database) + * + * @throws RuntimeException If the process failed + */ + public function addauthorbookmark(User $user): void + { + $userbookmark = new Bookmark(); + $uid = $user->id(); + $userbookmark->init( + "$uid-is-author", + "?authorfilter[0]=$uid&submit=filter", + '👤', + "$uid's pages", + "Pages where $uid is listed as an author", + ); + $userbookmark->setuser($user->id()); + $this->add($userbookmark); + } + /** * @param Bookmark|string $id string ID or bookmark * diff --git a/app/class/Modelldap.php b/app/class/Modelldap.php new file mode 100644 index 0000000..310421a --- /dev/null +++ b/app/class/Modelldap.php @@ -0,0 +1,74 @@ +ldapserver = $ldapserver; + $this->connection = @ldap_connect($this->ldapserver); + if ($this->connection === false) { + throw new RuntimeException('bad LDAP server syntax'); + } + $this->tree = $tree; + $this->u = $u; + ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3); + } + + /** + * Try to authenticate user against CLUB1 local LDAP server + * + * @param string $username + * @param string $password + * + * @return bool indicating if auth is a success + * + * @throws RuntimeException If LDAP connection failed + */ + public function auth(string $username, string $password): bool + { + $binddn = "$this->u=$username,$this->tree"; + + $ldapbind = @ldap_bind($this->connection, $binddn, $password); + if ($ldapbind === false) { + $errno = ldap_errno($this->connection); + switch ($errno) { + case self::LDAP_INVALID_CREDENTIALS: + return false; + } + throw new RuntimeException(ldap_err2str($errno)); + } + return true; + } + + public function disconnect(): void + { + ldap_close($this->connection); + } +} diff --git a/app/class/User.php b/app/class/User.php index 92d7741..dea7d1d 100644 --- a/app/class/User.php +++ b/app/class/User.php @@ -11,7 +11,7 @@ class User extends Item protected string $id = ''; protected int $level = 0; protected string $signature = ''; - protected ?string $password; + protected ?string $password = null; protected bool $passwordhashed = false; /** @var string $name Displayed name */ @@ -60,6 +60,15 @@ public function __construct($datas = []) $this->hydrate($datas); } + /** + * Indicate if User is authenticated using LDAP. + * It is if password is set to null. + */ + public function isldap(): bool + { + return (is_null($this->password)); + } + // _________________________ G E T _______________________ public function id() @@ -300,6 +309,14 @@ public function validpassword() return false; } + /** + * Set password to null value. This mean authentication depends on LDAP. + */ + public function removepassword(): void + { + $this->password = null; + } + /** * Generate new unique session ID and store it * @param string $info session info to store diff --git a/app/view/templates/admin.php b/app/view/templates/admin.php index be51b99..3affe76 100644 --- a/app/view/templates/admin.php +++ b/app/view/templates/admin.php @@ -362,6 +362,71 @@
++ W authenticates users with a password linked to their account, stored in your instance database. + If you have an LDAP server, you can choose to authenticate your users with this server instead, + rather than using W's database to store their password. + In this case, W will no longer allow user's passwords to be changed. +
+ ++ Address of the LDAP server. Should start with: + ldap:// or ldaps://. + Followed by the server address. + For a local server, put localhost. + A port can be specified by adding :port at the end. +
+ ++ + +
+ ++ The LDAP tree structure, but without the part containing user identifier. +
+ ++ + +
+ ++ The name of the user field in the LDAP database. +
+ ++ + +
+ + + ++ Users can be registered in LDAP but not have an account in W. + In this case, you can choose to have accounts created by defining the level of these new users. +
+ ++ + +
+Go back stop() ?> diff --git a/app/view/templates/profile.php b/app/view/templates/profile.php index 8651500..676e332 100644 --- a/app/view/templates/profile.php +++ b/app/view/templates/profile.php @@ -62,35 +62,37 @@ -