From 9ef2ecc184c72332f142061452f261e117d60986 Mon Sep 17 00:00:00 2001 From: Aisha Tammy Date: Mon, 4 Jul 2022 21:36:40 +0000 Subject: [PATCH 01/41] add SMTP email sending, LDAP authentication with auto user creation additional options are only visible when they are selected Signed-off-by: Aisha Tammy --- .../Frameworks/Baikal/Core/IMipSMTPPlugin.php | 197 ++++++++++++++++++ Core/Frameworks/Baikal/Core/LDAP.php | 166 +++++++++++++++ Core/Frameworks/Baikal/Core/Server.php | 9 +- .../Baikal/Model/Config/Standard.php | 74 ++++++- Core/Frameworks/BaikalAdmin/Resources/main.js | 36 ++++ Core/Frameworks/Formal/Element/Checkbox.php | 4 +- Core/Frameworks/Formal/Element/Listbox.php | 4 +- Core/Frameworks/Formal/Element/Text.php | 2 +- 8 files changed, 476 insertions(+), 16 deletions(-) create mode 100644 Core/Frameworks/Baikal/Core/IMipSMTPPlugin.php create mode 100644 Core/Frameworks/Baikal/Core/LDAP.php diff --git a/Core/Frameworks/Baikal/Core/IMipSMTPPlugin.php b/Core/Frameworks/Baikal/Core/IMipSMTPPlugin.php new file mode 100644 index 000000000..3dda372db --- /dev/null +++ b/Core/Frameworks/Baikal/Core/IMipSMTPPlugin.php @@ -0,0 +1,197 @@ + + * @license http://sabre.io/license/ Modified BSD License + */ +class IMipSMTPPlugin extends \Sabre\DAV\ServerPlugin +{ + /** + * Email address used in From: header. + * + * @var string + */ + protected $senderEmail; + + /** + * SMTP connection made by PEAR + * + */ + protected $smtp; + + /** + * ITipMessage. + * + * @var ITip\Message + */ + protected $itipMessage; + + /** + * Creates the email handler. + * + * @param string $senderEmail. The 'senderEmail' is the email that shows up + * in the 'From:' address. This should + * generally be some kind of no-reply email + * address you own. + */ + public function __construct($senderEmail, $smtp_host = "", $smtp_port = "", $smtp_username = "", $smtp_password = "") + { + require_once "Mail.php"; + $this->senderEmail = $senderEmail; + $this->smtp = \Mail::factory('smtp', array ('host' => $smtp_host, 'port' => $smtp_port, 'auth' => true, 'username' => $smtp_username, 'password' => $smtp_password)); + } + + /* + * This initializes the plugin. + * + * This function is called by Sabre\DAV\Server, after + * addPlugin is called. + * + * This method should set up the required event subscriptions. + * + * @param DAV\Server $server + * @return void + */ + public function initialize(DAV\Server $server) + { + $server->on('schedule', [$this, 'schedule'], 120); + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + public function getPluginName() + { + return 'imip-smtp'; + } + + /** + * Event handler for the 'schedule' event. + */ + public function schedule(ITip\Message $iTipMessage) + { + // Not sending any emails if the system considers the update + // insignificant. + if (!$iTipMessage->significantChange) { + if (!$iTipMessage->scheduleStatus) { + $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email'; + } + + return; + } + + $summary = $iTipMessage->message->VEVENT->SUMMARY; + + if ('mailto' !== parse_url($iTipMessage->sender, PHP_URL_SCHEME)) { + return; + } + + if ('mailto' !== parse_url($iTipMessage->recipient, PHP_URL_SCHEME)) { + return; + } + + $sender = substr($iTipMessage->sender, 7); + $recipient = substr($iTipMessage->recipient, 7); + + if ($iTipMessage->senderName) { + $sender = $iTipMessage->senderName.' <'.$sender.'>'; + } + if ($iTipMessage->recipientName && $iTipMessage->recipientName != $recipient) { + $recipient = $iTipMessage->recipientName.' <'.$recipient.'>'; + } + + $subject = 'SabreDAV iTIP message'; + switch (strtoupper($iTipMessage->method)) { + case 'REPLY': + $subject = 'Re: '.$summary; + break; + case 'REQUEST': + $subject = 'Invitation: '.$summary; + break; + case 'CANCEL': + $subject = 'Cancelled: '.$summary; + break; + } + + $headers = array( + 'Reply-To' => $sender, + 'From' => $iTipMessage->senderName.' <'.$this->senderEmail.'>', + 'To' => $recipient, + 'Subject' => $subject, + 'MIME-Version' => '1.0', + 'Content-Type' => 'text/calendar; charset=UTF-8; method='.$iTipMessage->method, + ); + if (DAV\Server::$exposeVersion) { + $headers += ['X-Sabre-Version' => DAV\Version::VERSION]; + } + $this->mail( + $recipient, + $headers, + $iTipMessage->message->serialize() + ); + $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip'; + } + + // @codeCoverageIgnoreStart + // This is deemed untestable in a reasonable manner + + /** + * This function is responsible for sending the actual email. + * + * @param string $to Recipient email address + * @param string $body iCalendar body + * @param array $headers List of headers + */ + protected function mail($to, array $headers, $body) + { + $mail = $this->smtp->send($to, $headers, $body); + if (\PEAR::isError($mail)) + error_log($mail->getMessage()); + else + error_log("Email successfully sent!"); + } + + // @codeCoverageIgnoreEnd + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + public function getPluginInfo() + { + return [ + 'name' => $this->getPluginName(), + 'description' => 'Email delivery (rfc6047) for CalDAV scheduling using Pear SMTP', + 'link' => 'http://sabre.io/dav/scheduling/', + ]; + } +} diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php new file mode 100644 index 000000000..426c46284 --- /dev/null +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -0,0 +1,166 @@ + + * @license http://sabre.io/license/ Modified BSD License + */ +class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic +{ + /** + * Reference to PDO connection. + * + * @var PDO + */ + protected $pdo; + + /** + * PDO table name we'll be using. + * + * @var string + */ + protected $table_name; + + /** + * LDAP server uri. + * e.g. ldaps://ldap.example.org + * + * @var string + */ + protected $ldap_uri; + + /* + * LDAP dn pattern for binding + * + * %u - gets replaced by full username + * %U - gets replaced by user part when the + * username is an email address + * %d - gets replaced by domain part when the + * username is an email address + * %1-9 - gets replaced by parts of the the domain + * split by '.' in reverse order + * mail.example.org: %1 = org, %2 = example, %3 = mail + * + * @var string + */ + protected $ldap_dn; + + /* + * LDAP attribute to use for name + * + * @var string + */ + protected $ldap_cn; + + /* + * LDAP attribute used for mail + * + * @var string + */ + protected $ldap_mail; + + /** + * Creates the backend object. + * + * @param string $ldap_uri + * @param string $ldap_dn + * @param string $ldap_cn + * @param string $ldap_mail + * + */ + public function __construct(\PDO $pdo, $table_name = 'users', $ldap_uri = 'ldap://127.0.0.1', $ldap_dn = 'mail=%u', $ldap_cn = 'cn', $ldap_mail = 'mail') + { + $this->pdo = $pdo; + $this->table_name = $table_name; + $this->ldap_uri = $ldap_uri; + $this->ldap_dn = $ldap_dn; + $this->ldap_cn = $ldap_cn; + $this->ldap_mail = $ldap_mail; + } + + /** + * Connects to an LDAP server and tries to authenticate. + * + * @param string $username + * @param string $password + * + * @return bool + */ + protected function ldapOpen($username, $password) + { + $conn = ldap_connect($this->ldap_uri); + if(!$conn) + return false; + if(!ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3)) + return false; + + $success = false; + + $user_split = explode('@', $username, 2); + $ldap_user = $user_split[0]; + $ldap_domain = ''; + if (count($user_split) > 1) + $ldap_domain = $user_split[1]; + $domain_split = array_reverse(explode('.', $ldap_domain)); + + $dn = str_replace('%u', $username, $this->ldap_dn); + $dn = str_replace('%U', $ldap_user, $dn); + $dn = str_replace('%d', $ldap_domain, $dn); + for($i = 1; $i <= count($domain_split) and $i <= 9; $i++) + $dn = str_replace('%' . $i, $domain_split[$i - 1], $dn); + + try { + $bind = ldap_bind($conn, $dn, $password); + if ($bind) { + $success = true; + } + } catch (\ErrorException $e) { + error_log($e->getMessage()); + error_log(ldap_error($conn)); + } + + if($success){ + $stmt = $this->pdo->prepare('SELECT username, digesta1 FROM ' . $this->table_name . ' WHERE username = ?'); + $stmt->execute([$username]); + $result = $stmt->fetchAll(); + + if (empty($result)) { + $search_results = ldap_read($conn, $dn, '(objectclass=*)', array($this->ldap_cn, $this->ldap_mail)); + $entry = ldap_get_entries($conn, $search_results); + $user_displayname = $username; + $user_email = 'unset-email'; + if (!empty($entry[0][$this->ldap_cn])) + $user_displayname = $entry[0][$this->ldap_cn][0]; + if (!empty($entry[0][$this->ldap_mail])) + $user_email = $entry[0][$this->ldap_mail][0]; + + $user = new \Baikal\Model\User(); + $user->set('username', $username); + $user->set('displayname', $user_displayname); + $user->set('email', $user_email); + $user->persist(); + } + } + + ldap_close($conn); + + return $success; + } + + /** + * Validates a username and password by trying to authenticate against LDAP. + * + * @param string $username + * @param string $password + * + * @return bool + */ + protected function validateUserPass($username, $password) + { + return $this->ldapOpen($username, $password); + } +} diff --git a/Core/Frameworks/Baikal/Core/Server.php b/Core/Frameworks/Baikal/Core/Server.php index f08c11000..48b8de212 100644 --- a/Core/Frameworks/Baikal/Core/Server.php +++ b/Core/Frameworks/Baikal/Core/Server.php @@ -135,6 +135,8 @@ protected function initServer() { $authBackend = new \Baikal\Core\PDOBasicAuth($this->pdo, $this->authRealm); } elseif ($this->authType === 'Apache') { $authBackend = new \Sabre\DAV\Auth\Backend\Apache(); + } elseif ($this->authType === 'LDAP') { + $authBackend = new \Baikal\Core\LDAP($this->pdo, 'users', $config['system']['ldap_uri'], $config['system']['ldap_dn'], $config['system']['ldap_cn'], $config['system']['ldap_mail']); } else { $authBackend = new \Sabre\DAV\Auth\Backend\PDO($this->pdo); $authBackend->setRealm($this->authRealm); @@ -174,7 +176,12 @@ protected function initServer() { $this->server->addPlugin(new \Sabre\DAV\Sharing\Plugin()); $this->server->addPlugin(new \Sabre\CalDAV\SharingPlugin()); if (isset($config['system']["invite_from"]) && $config['system']["invite_from"] !== "") { - $this->server->addPlugin(new \Sabre\CalDAV\Schedule\IMipPlugin($config['system']["invite_from"])); + if($config['system']['use_smtp']) { + $this->server->addPlugin(new \Baikal\Core\IMipSMTPPlugin($config['system']["invite_from"], $config['system']['smtp_host'], $config['system']['smtp_port'], $config['system']['smtp_username'], $config['system']['smtp_password'])); + } + else { + $this->server->addPlugin(new \Sabre\CalDAV\Schedule\IMipPlugin($config['system']["invite_from"])); + } } } if ($this->enableCardDAV) { diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 310d512d4..3bc5bbf01 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -32,17 +32,26 @@ class Standard extends \Baikal\Model\Config { # Default values protected $aData = [ - "configured_version" => BAIKAL_VERSION, - "timezone" => "Europe/Paris", - "card_enabled" => true, - "cal_enabled" => true, - "dav_auth_type" => "Digest", - "admin_passwordhash" => "", - "failed_access_message" => "user %u authentication failure for Baikal", + "configured_version" => BAIKAL_VERSION, + "timezone" => "Europe/Paris", + "card_enabled" => true, + "cal_enabled" => true, + "dav_auth_type" => "Digest", + "ldap_uri" => "ldap://127.0.0.1", + "ldap_dn" => "mail=%u", + "ldap_cn" => "cn", + "ldap_mail" => "mail", + "use_smtp" => false, + "smtp_username" => "", + "smtp_password" => "", + "smtp_host" => "", + "smtp_port" => "465", + "admin_passwordhash" => "", + "failed_access_message" => "user %u authentication failure for Baikal", // While not editable as will change admin & any existing user passwords, // could be set to different value when migrating from legacy config - "auth_realm" => "BaikalDAV", - "base_uri" => "", + "auth_realm" => "BaikalDAV", + "base_uri" => "", ]; function __construct() { @@ -76,10 +85,55 @@ function formMorphologyForThisModelInstance() { "help" => "Leave empty to disable sending invite emails", ])); + $oMorpho->add(new \Formal\Element\Checkbox([ + "prop" => "use_smtp", + "label" => "Use SMTP for sending emails", + ])); + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "smtp_username", + "label" => "Username for SMTP server", + ])); + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "smtp_password", + "label" => "Password for SMTP server", + ])); + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "smtp_host", + "label" => "SMTP server address", + ])); + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "smtp_port", + "label" => "SMTP server port", + ])); + $oMorpho->add(new \Formal\Element\Listbox([ "prop" => "dav_auth_type", "label" => "WebDAV authentication type", - "options" => ["Digest", "Basic", "Apache"], + "options" => ["Digest", "Basic", "Apache", "LDAP"], + ])); + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "ldap_uri", + "label" => "URI of the LDAP server; default ldap://127.0.0.1", + ])); + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "ldap_dn", + "label" => "User DN for bind; with replacments %u => username, %U => user part, %d => domain part of username, %1-9 parts of the domain in reverse order", + ])); + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "ldap_cn", + "label" => "LDAP-attribute for displayname; default cn", + ])); + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "ldap_mail", + "label" => "LDAP-attribute for email; default mail", ])); $oMorpho->add(new \Formal\Element\Password([ diff --git a/Core/Frameworks/BaikalAdmin/Resources/main.js b/Core/Frameworks/BaikalAdmin/Resources/main.js index 0414853a7..430a5af7a 100644 --- a/Core/Frameworks/BaikalAdmin/Resources/main.js +++ b/Core/Frameworks/BaikalAdmin/Resources/main.js @@ -25,3 +25,39 @@ function copyToClipboard(el) { $(el).css({backgroundColor:"#75c753"}); $(el).animate({backgroundColor:"transparent"}, 1500); } + +function hideDepends(el, dep, val) { + var el_tohide = document.getElementById(el); + var el_dep = document.getElementById(dep); + if (!el_tohide || !el_dep){ + return; + } + var visibility = "none"; + if(typeof(val) == "boolean"){ + if(el_dep.checked == val) + visibility = "block"; + } else { + if(el_dep.value == val) + visibility = "block"; + } + el_tohide.style.display = visibility; +} + +hideDependsArray=[ + ["control-group-smtp_username", "use_smtp", true], + ["control-group-smtp_password", "use_smtp", true], + ["control-group-smtp_host", "use_smtp", true], + ["control-group-smtp_port", "use_smtp", true], + ["control-group-ldap_uri", "dav_auth_type", "LDAP"], + ["control-group-ldap_dn", "dav_auth_type", "LDAP"], + ["control-group-ldap_cn", "dav_auth_type", "LDAP"], + ["control-group-ldap_mail", "dav_auth_type", "LDAP"], +]; + +function hideDependsAll() { + hideDependsArray.forEach(function (element, index) { + hideDepends(element[0], element[1], element[2]); + }); +} + +hideDependsAll(); diff --git a/Core/Frameworks/Formal/Element/Checkbox.php b/Core/Frameworks/Formal/Element/Checkbox.php index 39649c465..06907eee0 100644 --- a/Core/Frameworks/Formal/Element/Checkbox.php +++ b/Core/Frameworks/Formal/Element/Checkbox.php @@ -71,10 +71,10 @@ function render() { } $sHtml = << +
- + {$helpblock}
diff --git a/Core/Frameworks/Formal/Element/Listbox.php b/Core/Frameworks/Formal/Element/Listbox.php index f398965c5..37d175068 100644 --- a/Core/Frameworks/Formal/Element/Listbox.php +++ b/Core/Frameworks/Formal/Element/Listbox.php @@ -89,10 +89,10 @@ function render() { unset($aRenderedOptions); $sHtml = << +
- {$sRenderedOptions} {$helpblock} diff --git a/Core/Frameworks/Formal/Element/Text.php b/Core/Frameworks/Formal/Element/Text.php index 0690ef757..a97762d6b 100644 --- a/Core/Frameworks/Formal/Element/Text.php +++ b/Core/Frameworks/Formal/Element/Text.php @@ -87,7 +87,7 @@ function render() { } $sHtml = << +
From 74e92aa3c48bc3cdce3d16e3b3fc552cd8cc9318 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Tue, 19 Jul 2022 20:13:32 +0200 Subject: [PATCH 02/41] Added support for LDAP filters and attribute search --- Core/Frameworks/Baikal/Core/LDAP.php | 186 +++++++++++++++--- Core/Frameworks/Baikal/Core/Server.php | 2 +- .../Baikal/Model/Config/Standard.php | 38 ++++ Core/Frameworks/BaikalAdmin/Resources/main.js | 13 +- 4 files changed, 205 insertions(+), 34 deletions(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 426c46284..fe99b6e8a 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -7,6 +7,7 @@ * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Aisha Tammy + * @author El-Virus * @license http://sabre.io/license/ Modified BSD License */ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic @@ -25,6 +26,15 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic */ protected $table_name; + /** + * LDAP mode. + * Defines if LDAP authentication should match + * by DN, Attribute, or Filter. + * + * @var string + */ + protected $ldap_mode; + /** * LDAP server uri. * e.g. ldaps://ldap.example.org @@ -33,8 +43,25 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic */ protected $ldap_uri; - /* - * LDAP dn pattern for binding + /** + * LDAP bind dn. + * Defines the bind dn that Baikal is going to use + * when looking for an attribute or filtering. + * + * @var string + */ + protected $ldap_bind_dn; + + /** + * LDAP bind password. + * Defines the password used by Baikal for binding. + * + * @var string + */ + protected $ldap_bind_password; + + /** + * LDAP dn pattern for binding. * * %u - gets replaced by full username * %U - gets replaced by user part when the @@ -49,20 +76,90 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic */ protected $ldap_dn; - /* - * LDAP attribute to use for name + /** + * LDAP attribute to use for name. * * @var string */ protected $ldap_cn; - /* - * LDAP attribute used for mail + /** + * LDAP attribute used for mail. * * @var string */ protected $ldap_mail; + /** + * LDAP base path where to search for attributes + * and apply filters. + * + * @var string + */ + protected $ldap_search_base; + + /** + * LDAP attribute to search for. + * + * @var string + */ + protected $ldap_search_attribute; + + /** + * LDAP filter to apply. + * + * @var string + */ + protected $ldap_search_filter; + + /** + * Replaces patterns for their assigned value. + * + * @param string &$base + * @param string $username + * + */ + + protected function patternReplace(&$base, $username) + { + $user_split = explode('@', $username, 2); + $ldap_user = $user_split[0]; + $ldap_domain = ''; + if (count($user_split) > 1) + $ldap_domain = $user_split[1]; + $domain_split = array_reverse(explode('.', $ldap_domain)); + + $base = str_replace('%u', $username, $base); + $base = str_replace('%U', $ldap_user, $base); + $base = str_replace('%d', $ldap_domain, $base); + for($i = 1; $i <= count($domain_split) and $i <= 9; $i++) + $base = str_replace('%' . $i, $domain_split[$i - 1], $base); + } + + /** + * Checks if a user can bind with a password. + * If an error is produced, it will be logged. + * + * @param \LDAP\Connection &$conn + * @param string $dn + * @param string $password + * + * @return bool + */ + + protected function doesBind(&$conn, $dn, $password) + { + try { + $bind = ldap_bind($conn, $dn, $password); + if ($bind) + return true; + } catch (\ErrorException $e) { + error_log($e->getMessage()); + error_log(ldap_error($conn)); + } + return false; + } + /** * Creates the backend object. * @@ -72,14 +169,20 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic * @param string $ldap_mail * */ - public function __construct(\PDO $pdo, $table_name = 'users', $ldap_uri = 'ldap://127.0.0.1', $ldap_dn = 'mail=%u', $ldap_cn = 'cn', $ldap_mail = 'mail') + public function __construct(\PDO $pdo, $table_name = 'users', $ldap_mode = 'DN', $ldap_uri = 'ldap://127.0.0.1', $ldap_bind_dn = 'cn=baikal,ou=apps,dc=example,dc=com', $ldap_bind_password = '', $ldap_dn = 'mail=%u', $ldap_cn = 'cn', $ldap_mail = 'mail', $ldap_search_base = 'ou=users,dc=example,dc=com', $ldap_search_attribute = 'uid=%U', $ldap_search_filter = '(objectClass=*)') { - $this->pdo = $pdo; - $this->table_name = $table_name; - $this->ldap_uri = $ldap_uri; - $this->ldap_dn = $ldap_dn; - $this->ldap_cn = $ldap_cn; - $this->ldap_mail = $ldap_mail; + $this->pdo = $pdo; + $this->table_name = $table_name; + $this->ldap_mode = $ldap_mode; + $this->ldap_uri = $ldap_uri; + $this->ldap_bind_dn = $ldap_bind_dn; + $this->ldap_bind_password = $ldap_bind_password; + $this->ldap_dn = $ldap_dn; + $this->ldap_cn = $ldap_cn; + $this->ldap_mail = $ldap_mail; + $this->ldap_search_base = $ldap_search_base; + $this->ldap_search_attribute = $ldap_search_attribute; + $this->ldap_search_filter = $ldap_search_filter; } /** @@ -100,29 +203,50 @@ protected function ldapOpen($username, $password) $success = false; - $user_split = explode('@', $username, 2); - $ldap_user = $user_split[0]; - $ldap_domain = ''; - if (count($user_split) > 1) - $ldap_domain = $user_split[1]; - $domain_split = array_reverse(explode('.', $ldap_domain)); + if ($this->ldap_mode == 'DN') { + $this->patternReplace($dn, $username); - $dn = str_replace('%u', $username, $this->ldap_dn); - $dn = str_replace('%U', $ldap_user, $dn); - $dn = str_replace('%d', $ldap_domain, $dn); - for($i = 1; $i <= count($domain_split) and $i <= 9; $i++) - $dn = str_replace('%' . $i, $domain_split[$i - 1], $dn); + $success = $this->doesBind($conn, $dn, $password); + } else if ($this->ldap_mode == 'Attribute') { + try { + if (!$this->doesBind($conn, $this->ldap_bind_dn, $this->ldap_bind_password)) { + return false; + } - try { - $bind = ldap_bind($conn, $dn, $password); - if ($bind) { - $success = true; + $attribute = $this->ldap_search_attribute; + $this->patternReplace($attribute, $username); + + $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, '('.$attribute.')', [explode('=', $attribute, 2)[0]], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; + + $dn = $result["dn"]; + $success = $this->doesBind($conn, $dn, $password); + } catch (\ErrorException $e) { + error_log($e->getMessage()); + error_log(ldap_error($conn)); } - } catch (\ErrorException $e) { - error_log($e->getMessage()); - error_log(ldap_error($conn)); + } else if ($this->ldap_mode == 'Filter') { + try { + if (!$this->doesBind($conn, $this->ldap_bind_dn, $this->ldap_bind_password)) { + return false; + } + + $filter = $this->ldap_search_filter; + $this->patternReplace($filter, $username); + + $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, $filter, [], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; + + $dn = $result["dn"]; + $success = $this->doesBind($conn, $dn, $password); + } catch (\ErrorException $e) { + error_log($e->getMessage()); + error_log(ldap_error($conn)); + } + } else { + error_log('Unknown LDAP authentication mode'); } + + if($success){ $stmt = $this->pdo->prepare('SELECT username, digesta1 FROM ' . $this->table_name . ' WHERE username = ?'); $stmt->execute([$username]); diff --git a/Core/Frameworks/Baikal/Core/Server.php b/Core/Frameworks/Baikal/Core/Server.php index 48b8de212..7c64c1772 100644 --- a/Core/Frameworks/Baikal/Core/Server.php +++ b/Core/Frameworks/Baikal/Core/Server.php @@ -136,7 +136,7 @@ protected function initServer() { } elseif ($this->authType === 'Apache') { $authBackend = new \Sabre\DAV\Auth\Backend\Apache(); } elseif ($this->authType === 'LDAP') { - $authBackend = new \Baikal\Core\LDAP($this->pdo, 'users', $config['system']['ldap_uri'], $config['system']['ldap_dn'], $config['system']['ldap_cn'], $config['system']['ldap_mail']); + $authBackend = new \Baikal\Core\LDAP($this->pdo, 'users', $config['system']['ldap_mode'], $config['system']['ldap_uri'], $config['system']['ldap_bind_dn'], $config['system']['ldap_bind_password'], $config['system']['ldap_dn'], $config['system']['ldap_cn'], $config['system']['ldap_mail'], $config['system']['ldap_search_base'], $config['system']['ldap_search_attribute'], $config['system']['ldap_search_filter']); } else { $authBackend = new \Sabre\DAV\Auth\Backend\PDO($this->pdo); $authBackend->setRealm($this->authRealm); diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 3bc5bbf01..9605b0f36 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -37,10 +37,16 @@ class Standard extends \Baikal\Model\Config { "card_enabled" => true, "cal_enabled" => true, "dav_auth_type" => "Digest", + "ldap_mode" => "None", "ldap_uri" => "ldap://127.0.0.1", + "ldap_bind_dn" => "cn=baikal,ou=apps,dc=example,dc=com", + "ldap_bind_password" => "", "ldap_dn" => "mail=%u", "ldap_cn" => "cn", "ldap_mail" => "mail", + "ldap_search_base" => "ou=users,dc=example,dc=com", + "ldap_search_attribute" => "uid=%U", + "ldap_search_filter" => "(objectClass=*)", "use_smtp" => false, "smtp_username" => "", "smtp_password" => "", @@ -116,11 +122,27 @@ function formMorphologyForThisModelInstance() { "options" => ["Digest", "Basic", "Apache", "LDAP"], ])); + $oMorpho->add(new \Formal\Element\Listbox([ + "prop" => "ldap_mode", + "label" => "LDAP authentication mode", + "options" => ["DN", "Attribute", "Filter"], + ])); + $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_uri", "label" => "URI of the LDAP server; default ldap://127.0.0.1", ])); + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "ldap_bind_dn", + "label" => "DN which Baikal will use to bind to the LDAP server", + ])); + + $oMorpho->add(new \Formal\Element\Password([ + "prop" => "ldap_bind_password", + "label" => "The password of the bind DN user", + ])); + $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_dn", "label" => "User DN for bind; with replacments %u => username, %U => user part, %d => domain part of username, %1-9 parts of the domain in reverse order", @@ -136,6 +158,22 @@ function formMorphologyForThisModelInstance() { "label" => "LDAP-attribute for email; default mail", ])); + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "ldap_search_base", + "label" => "The base of the LDAP search", + ])); + + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "ldap_search_attribute", + "label" => "Attribute and match.; with replacments %u => username, %U => user part, %d => domain part of username, %1-9 parts of the domain in reverse order", + ])); + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "ldap_search_filter", + "label" => "The LDAP filter to be applied to the search.", + ])); + $oMorpho->add(new \Formal\Element\Password([ "prop" => "admin_passwordhash", "label" => "Admin password", diff --git a/Core/Frameworks/BaikalAdmin/Resources/main.js b/Core/Frameworks/BaikalAdmin/Resources/main.js index 430a5af7a..a2dfc7fb3 100644 --- a/Core/Frameworks/BaikalAdmin/Resources/main.js +++ b/Core/Frameworks/BaikalAdmin/Resources/main.js @@ -36,9 +36,12 @@ function hideDepends(el, dep, val) { if(typeof(val) == "boolean"){ if(el_dep.checked == val) visibility = "block"; - } else { + } else if (typeof(val) == string) { if(el_dep.value == val) visibility = "block"; + } else { + if(val.includes(el_dep.value)) + visibility = "block"; } el_tohide.style.display = visibility; } @@ -48,10 +51,16 @@ hideDependsArray=[ ["control-group-smtp_password", "use_smtp", true], ["control-group-smtp_host", "use_smtp", true], ["control-group-smtp_port", "use_smtp", true], + ["control-group-ldap_mode", "dav_auth_type", "LDAP"], ["control-group-ldap_uri", "dav_auth_type", "LDAP"], - ["control-group-ldap_dn", "dav_auth_type", "LDAP"], ["control-group-ldap_cn", "dav_auth_type", "LDAP"], ["control-group-ldap_mail", "dav_auth_type", "LDAP"], + ["control-group-ldap_dn", "ldap_mode", "DN"], + ["control-group-ldap_bind_dn", "ldap_mode", ["Attribute", "Filter"]], + ["control-group-ldap_bind_password", "ldap_mode", ["Attribute", "Filter"]], + ["control-group-ldap_search_base", "ldap_mode", ["Attribute", "Filter"]], + ["control-group-ldap_search_attribute", "ldap_mode", "Attribute"], + ["control-group-ldap_search_filter", "ldap_mode", "Filter"], ]; function hideDependsAll() { From 6cc928e050a77789622a8d450ac60ebba76fe254 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Tue, 19 Jul 2022 21:30:56 +0200 Subject: [PATCH 03/41] Removed new field hiding system and changed LDAP and SMTP parameters to use a modified version of the old one --- .../Baikal/Model/Config/Standard.php | 3 ++ .../Controller/Settings/Standard.php | 54 +++++++++++++++++++ Core/Frameworks/BaikalAdmin/Resources/main.js | 47 +--------------- Core/Frameworks/Formal/Element/Checkbox.php | 6 +-- Core/Frameworks/Formal/Element/Listbox.php | 9 +++- Core/Frameworks/Formal/Element/Text.php | 2 +- 6 files changed, 69 insertions(+), 52 deletions(-) diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 9605b0f36..d5c60f2ed 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -94,6 +94,7 @@ function formMorphologyForThisModelInstance() { $oMorpho->add(new \Formal\Element\Checkbox([ "prop" => "use_smtp", "label" => "Use SMTP for sending emails", + "refreshonchange" => true, ])); $oMorpho->add(new \Formal\Element\Text([ @@ -120,12 +121,14 @@ function formMorphologyForThisModelInstance() { "prop" => "dav_auth_type", "label" => "WebDAV authentication type", "options" => ["Digest", "Basic", "Apache", "LDAP"], + "refreshonchange" => true, ])); $oMorpho->add(new \Formal\Element\Listbox([ "prop" => "ldap_mode", "label" => "LDAP authentication mode", "options" => ["DN", "Attribute", "Filter"], + "refreshonchange" => true, ])); $oMorpho->add(new \Formal\Element\Text([ diff --git a/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php b/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php index 204f31b87..cf5322eda 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php @@ -27,6 +27,8 @@ namespace BaikalAdmin\Controller\Settings; +use Symfony\Component\Yaml\Yaml; + class Standard extends \Flake\Core\Controller { /** * @var \Baikal\Model\Config\Standard @@ -48,6 +50,7 @@ function execute() { $this->oForm = $this->oModel->formForThisModelInstance([ "close" => false, + "hook.morphology" => [$this, "morphologyHook"], ]); if ($this->oForm->submitted()) { @@ -61,4 +64,55 @@ function render() { return $oView->render(); } + + function morphologyHook(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { + if ($oForm->submitted()) { + $bSMTP = (intval($oForm->postValue("use_smtp")) === 1); + $bLDAP = (strval($oForm->postValue("dav_auth_type")) === "LDAP"); + $sLDAPm = strval($oForm->postValue("ldap_mode")); + } else { + try { + $config = Yaml::parseFile(PROJECT_PATH_CONFIG . "baikal.yaml"); + } catch (\Exception $e) { + error_log('Error reading baikal.yaml file : ' . $e->getMessage()); + } + $bSMTP = $config['system']['use_smtp'] ?? true; + $bLDAP = isset($config['system']['dav_auth_type']) ? ($config['system']['dav_auth_type'] === 'LDAP') : false; + $sLDAPm = $config['system']['ldap_mode'] ?? 'DN'; + } + + if ($bSMTP) { + $oMorpho->remove("invite_from"); + } else { + $oMorpho->remove("smtp_username"); + $oMorpho->remove("smtp_password"); + $oMorpho->remove("smtp_host"); + $oMorpho->remove("smtp_port"); + } + if ($bLDAP) { + if ($sLDAPm === "DN") { + $oMorpho->remove("ldap_bind_dn"); + $oMorpho->remove("ldap_bind_password"); + $oMorpho->remove("ldap_search_base"); + $oMorpho->remove("ldap_search_attribute"); + $oMorpho->remove("ldap_search_filter"); + } elseif ($sLDAPm === "Attribute") { + $oMorpho->remove("ldap_dn"); + $oMorpho->remove("ldap_search_filter"); + } elseif ($sLDAPm === "Filter") { + $oMorpho->remove("ldap_dn"); + $oMorpho->remove("ldap_search_attribute"); + } + } else { + $oMorpho->remove("ldap_mode"); + $oMorpho->remove("ldap_bind_dn"); + $oMorpho->remove("ldap_bind_password"); + $oMorpho->remove("ldap_dn"); + $oMorpho->remove("ldap_cn"); + $oMorpho->remove("ldap_mail"); + $oMorpho->remove("ldap_search_base"); + $oMorpho->remove("ldap_search_attribute"); + $oMorpho->remove("ldap_search_filter"); + } + } } diff --git a/Core/Frameworks/BaikalAdmin/Resources/main.js b/Core/Frameworks/BaikalAdmin/Resources/main.js index a2dfc7fb3..1cbd9d9c9 100644 --- a/Core/Frameworks/BaikalAdmin/Resources/main.js +++ b/Core/Frameworks/BaikalAdmin/Resources/main.js @@ -24,49 +24,4 @@ function copyToClipboard(el) { sel.removeAllRanges(); $(el).css({backgroundColor:"#75c753"}); $(el).animate({backgroundColor:"transparent"}, 1500); -} - -function hideDepends(el, dep, val) { - var el_tohide = document.getElementById(el); - var el_dep = document.getElementById(dep); - if (!el_tohide || !el_dep){ - return; - } - var visibility = "none"; - if(typeof(val) == "boolean"){ - if(el_dep.checked == val) - visibility = "block"; - } else if (typeof(val) == string) { - if(el_dep.value == val) - visibility = "block"; - } else { - if(val.includes(el_dep.value)) - visibility = "block"; - } - el_tohide.style.display = visibility; -} - -hideDependsArray=[ - ["control-group-smtp_username", "use_smtp", true], - ["control-group-smtp_password", "use_smtp", true], - ["control-group-smtp_host", "use_smtp", true], - ["control-group-smtp_port", "use_smtp", true], - ["control-group-ldap_mode", "dav_auth_type", "LDAP"], - ["control-group-ldap_uri", "dav_auth_type", "LDAP"], - ["control-group-ldap_cn", "dav_auth_type", "LDAP"], - ["control-group-ldap_mail", "dav_auth_type", "LDAP"], - ["control-group-ldap_dn", "ldap_mode", "DN"], - ["control-group-ldap_bind_dn", "ldap_mode", ["Attribute", "Filter"]], - ["control-group-ldap_bind_password", "ldap_mode", ["Attribute", "Filter"]], - ["control-group-ldap_search_base", "ldap_mode", ["Attribute", "Filter"]], - ["control-group-ldap_search_attribute", "ldap_mode", "Attribute"], - ["control-group-ldap_search_filter", "ldap_mode", "Filter"], -]; - -function hideDependsAll() { - hideDependsArray.forEach(function (element, index) { - hideDepends(element[0], element[1], element[2]); - }); -} - -hideDependsAll(); +} \ No newline at end of file diff --git a/Core/Frameworks/Formal/Element/Checkbox.php b/Core/Frameworks/Formal/Element/Checkbox.php index 06907eee0..fe800b9d2 100644 --- a/Core/Frameworks/Formal/Element/Checkbox.php +++ b/Core/Frameworks/Formal/Element/Checkbox.php @@ -71,10 +71,10 @@ function render() { } $sHtml = << +
- + {$helpblock}
@@ -82,4 +82,4 @@ function render() { return $sHtml . $this->renderWitness(); } -} +} \ No newline at end of file diff --git a/Core/Frameworks/Formal/Element/Listbox.php b/Core/Frameworks/Formal/Element/Listbox.php index 37d175068..a241cd5d5 100644 --- a/Core/Frameworks/Formal/Element/Listbox.php +++ b/Core/Frameworks/Formal/Element/Listbox.php @@ -39,6 +39,7 @@ function render() { $prop = $this->option("prop"); $helpblock = ""; $popover = ""; + $onchange = ""; if ($this->option("readonly") === true) { $inputclass .= " disabled"; @@ -64,6 +65,10 @@ function render() { $popover .= " data-content=\"" . htmlspecialchars($aPopover["content"]) . "\" "; } + if ($this->option("refreshonchange") === true) { + $onchange = " onchange=\"document.getElementsByTagName('form')[0].elements['refreshed'].value=1;document.getElementsByTagName('form')[0].submit();\" "; + } + $clientvalue = htmlspecialchars($value); $aRenderedOptions = []; @@ -89,10 +94,10 @@ function render() { unset($aRenderedOptions); $sHtml = << +
- {$sRenderedOptions} {$helpblock} diff --git a/Core/Frameworks/Formal/Element/Text.php b/Core/Frameworks/Formal/Element/Text.php index a97762d6b..0690ef757 100644 --- a/Core/Frameworks/Formal/Element/Text.php +++ b/Core/Frameworks/Formal/Element/Text.php @@ -87,7 +87,7 @@ function render() { } $sHtml = << +
From 32aeff23ef9bd05d07a7d22a775501c9e36ad47b Mon Sep 17 00:00:00 2001 From: El-Virus Date: Wed, 20 Jul 2022 13:48:00 +0200 Subject: [PATCH 04/41] Add support for LDAP Groups, minor fixes --- Core/Frameworks/Baikal/Core/LDAP.php | 40 ++++++++++++++++--- Core/Frameworks/Baikal/Core/Server.php | 2 +- .../Baikal/Model/Config/Standard.php | 10 ++++- .../Controller/Settings/Standard.php | 9 +++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index fe99b6e8a..d926b0431 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -112,6 +112,13 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic */ protected $ldap_search_filter; + /** + * LDAP group to check if a user is member of. + * + * @var string + */ + protected $ldap_group; + /** * Replaces patterns for their assigned value. * @@ -169,7 +176,7 @@ protected function doesBind(&$conn, $dn, $password) * @param string $ldap_mail * */ - public function __construct(\PDO $pdo, $table_name = 'users', $ldap_mode = 'DN', $ldap_uri = 'ldap://127.0.0.1', $ldap_bind_dn = 'cn=baikal,ou=apps,dc=example,dc=com', $ldap_bind_password = '', $ldap_dn = 'mail=%u', $ldap_cn = 'cn', $ldap_mail = 'mail', $ldap_search_base = 'ou=users,dc=example,dc=com', $ldap_search_attribute = 'uid=%U', $ldap_search_filter = '(objectClass=*)') + public function __construct(\PDO $pdo, $table_name = 'users', $ldap_mode = 'DN', $ldap_uri = 'ldap://127.0.0.1', $ldap_bind_dn = 'cn=baikal,ou=apps,dc=example,dc=com', $ldap_bind_password = '', $ldap_dn = 'mail=%u', $ldap_cn = 'cn', $ldap_mail = 'mail', $ldap_search_base = 'ou=users,dc=example,dc=com', $ldap_search_attribute = 'uid=%U', $ldap_search_filter = '(objectClass=*)', $ldap_group = 'cn=baikal,ou=groups,dc=example,dc=com') { $this->pdo = $pdo; $this->table_name = $table_name; @@ -183,6 +190,7 @@ public function __construct(\PDO $pdo, $table_name = 'users', $ldap_mode = 'DN', $this->ldap_search_base = $ldap_search_base; $this->ldap_search_attribute = $ldap_search_attribute; $this->ldap_search_filter = $ldap_search_filter; + $this->ldap_group = $ldap_group; } /** @@ -207,7 +215,7 @@ protected function ldapOpen($username, $password) $this->patternReplace($dn, $username); $success = $this->doesBind($conn, $dn, $password); - } else if ($this->ldap_mode == 'Attribute') { + } elseif ($this->ldap_mode == 'Attribute' || $this->ldap_mode == 'Group') { try { if (!$this->doesBind($conn, $this->ldap_bind_dn, $this->ldap_bind_password)) { return false; @@ -219,12 +227,36 @@ protected function ldapOpen($username, $password) $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, '('.$attribute.')', [explode('=', $attribute, 2)[0]], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; $dn = $result["dn"]; + + if ($this->ldap_mode == 'Group') { + $inGroup = FALSE; + $members = ldap_get_entries($conn, ldap_read($conn, $this->ldap_group, '(objectClass=*)', ['member', 'uniqueMember'], 0, 0, 0, LDAP_DEREF_NEVER, []))[0]; + if (isset($members["member"])) { + foreach ($members["member"] as $member) { + if ($member == $result["dn"]) { + $inGroup = TRUE; + break; + } + } + } + if (isset($members["uniqueMember"])) { + foreach ($members["uniqueMember"] as $member) { + if ($member == $result["dn"]) { + $inGroup = TRUE; + break; + } + } + } + if (!$inGroup) + return false; + } + $success = $this->doesBind($conn, $dn, $password); } catch (\ErrorException $e) { error_log($e->getMessage()); error_log(ldap_error($conn)); } - } else if ($this->ldap_mode == 'Filter') { + } elseif ($this->ldap_mode == 'Filter') { try { if (!$this->doesBind($conn, $this->ldap_bind_dn, $this->ldap_bind_password)) { return false; @@ -245,8 +277,6 @@ protected function ldapOpen($username, $password) error_log('Unknown LDAP authentication mode'); } - - if($success){ $stmt = $this->pdo->prepare('SELECT username, digesta1 FROM ' . $this->table_name . ' WHERE username = ?'); $stmt->execute([$username]); diff --git a/Core/Frameworks/Baikal/Core/Server.php b/Core/Frameworks/Baikal/Core/Server.php index 7c64c1772..39434a87b 100644 --- a/Core/Frameworks/Baikal/Core/Server.php +++ b/Core/Frameworks/Baikal/Core/Server.php @@ -136,7 +136,7 @@ protected function initServer() { } elseif ($this->authType === 'Apache') { $authBackend = new \Sabre\DAV\Auth\Backend\Apache(); } elseif ($this->authType === 'LDAP') { - $authBackend = new \Baikal\Core\LDAP($this->pdo, 'users', $config['system']['ldap_mode'], $config['system']['ldap_uri'], $config['system']['ldap_bind_dn'], $config['system']['ldap_bind_password'], $config['system']['ldap_dn'], $config['system']['ldap_cn'], $config['system']['ldap_mail'], $config['system']['ldap_search_base'], $config['system']['ldap_search_attribute'], $config['system']['ldap_search_filter']); + $authBackend = new \Baikal\Core\LDAP($this->pdo, 'users', $config['system']['ldap_mode'], $config['system']['ldap_uri'], $config['system']['ldap_bind_dn'], $config['system']['ldap_bind_password'], $config['system']['ldap_dn'], $config['system']['ldap_cn'], $config['system']['ldap_mail'], $config['system']['ldap_search_base'], $config['system']['ldap_search_attribute'], $config['system']['ldap_search_filter'], $config['system']['ldap_group']); } else { $authBackend = new \Sabre\DAV\Auth\Backend\PDO($this->pdo); $authBackend->setRealm($this->authRealm); diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index d5c60f2ed..5a32437fa 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -47,6 +47,7 @@ class Standard extends \Baikal\Model\Config { "ldap_search_base" => "ou=users,dc=example,dc=com", "ldap_search_attribute" => "uid=%U", "ldap_search_filter" => "(objectClass=*)", + "ldap_group" => "cn=baikal,ou=groups,dc=example,dc=com", "use_smtp" => false, "smtp_username" => "", "smtp_password" => "", @@ -127,7 +128,7 @@ function formMorphologyForThisModelInstance() { $oMorpho->add(new \Formal\Element\Listbox([ "prop" => "ldap_mode", "label" => "LDAP authentication mode", - "options" => ["DN", "Attribute", "Filter"], + "options" => ["DN", "Attribute", "Filter", "Group"], "refreshonchange" => true, ])); @@ -169,7 +170,7 @@ function formMorphologyForThisModelInstance() { $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_search_attribute", - "label" => "Attribute and match.; with replacments %u => username, %U => user part, %d => domain part of username, %1-9 parts of the domain in reverse order", + "label" => "Attribute and match the user with.; with replacments %u => username, %U => user part, %d => domain part of username, %1-9 parts of the domain in reverse order", ])); $oMorpho->add(new \Formal\Element\Text([ @@ -177,6 +178,11 @@ function formMorphologyForThisModelInstance() { "label" => "The LDAP filter to be applied to the search.", ])); + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "ldap_group", + "label" => "The Group DN that contains the member atribute of the user.", + ])); + $oMorpho->add(new \Formal\Element\Password([ "prop" => "admin_passwordhash", "label" => "Admin password", diff --git a/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php b/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php index cf5322eda..7b7b14a84 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php @@ -96,12 +96,20 @@ function morphologyHook(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { $oMorpho->remove("ldap_search_base"); $oMorpho->remove("ldap_search_attribute"); $oMorpho->remove("ldap_search_filter"); + $oMorpho->remove("ldap_group"); } elseif ($sLDAPm === "Attribute") { $oMorpho->remove("ldap_dn"); $oMorpho->remove("ldap_search_filter"); + $oMorpho->remove("ldap_group"); } elseif ($sLDAPm === "Filter") { $oMorpho->remove("ldap_dn"); $oMorpho->remove("ldap_search_attribute"); + $oMorpho->remove("ldap_group"); + } elseif ($sLDAPm === "Group") { + $oMorpho->remove("ldap_dn"); + $oMorpho->remove("ldap_search_filter"); + } else { + error_log('Unknown LDAP mode: '.$sLDAPm); } } else { $oMorpho->remove("ldap_mode"); @@ -113,6 +121,7 @@ function morphologyHook(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { $oMorpho->remove("ldap_search_base"); $oMorpho->remove("ldap_search_attribute"); $oMorpho->remove("ldap_search_filter"); + $oMorpho->remove("ldap_group"); } } } From 16922d2bcaade0fe3e00c9983596e18154b7f7f5 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Sun, 24 Jul 2022 16:36:00 +0200 Subject: [PATCH 05/41] Added Morphology hook to Standard Settings Controller of Baikal Admin. --- .../BaikalAdmin/Controller/Settings/Standard.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php b/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php index 204f31b87..e77ae29cd 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php @@ -27,6 +27,8 @@ namespace BaikalAdmin\Controller\Settings; +use Symfony\Component\Yaml\Yaml; + class Standard extends \Flake\Core\Controller { /** * @var \Baikal\Model\Config\Standard @@ -48,6 +50,7 @@ function execute() { $this->oForm = $this->oModel->formForThisModelInstance([ "close" => false, + "hook.morphology" => [$this, "morphologyHook"], ]); if ($this->oForm->submitted()) { @@ -61,4 +64,16 @@ function render() { return $oView->render(); } + + function morphologyHook(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { + if ($oForm->submitted()) { + } else { + try { + $config = Yaml::parseFile(PROJECT_PATH_CONFIG . "baikal.yaml"); + } catch (\Exception $e) { + error_log('Error reading baikal.yaml file : ' . $e->getMessage()); + } + } + + } } From 11fd40daae087ed504480bd3d08f2d4da28d57c1 Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sun, 24 Jul 2022 15:21:00 +0000 Subject: [PATCH 06/41] Added missing refresh on "WebDAV authentication type" change --- Core/Frameworks/Baikal/Model/Config/Standard.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 7db734b72..734e56d36 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -91,6 +91,7 @@ function formMorphologyForThisModelInstance() { "prop" => "dav_auth_type", "label" => "WebDAV authentication type", "options" => ["Digest", "Basic", "Apache", "LDAP"], + "refreshonchange" => true, ])); $oMorpho->add(new \Formal\Element\Listbox([ From 248a4a848a16f9ca96decb110611570923d13271 Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sun, 24 Jul 2022 15:32:34 +0000 Subject: [PATCH 07/41] Fix LDAP.php, according to linter. Note that no period has been added after the example url, and places where an "if" statement doesn't contain a "{" haven't been changed. --- Core/Frameworks/Baikal/Core/LDAP.php | 55 ++++++++++++---------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index d926b0431..8cefaf04f 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -10,8 +10,7 @@ * @author El-Virus * @license http://sabre.io/license/ Modified BSD License */ -class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic -{ +class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic { /** * Reference to PDO connection. * @@ -30,7 +29,7 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic * LDAP mode. * Defines if LDAP authentication should match * by DN, Attribute, or Filter. - * + * * @var string */ protected $ldap_mode; @@ -47,7 +46,7 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic * LDAP bind dn. * Defines the bind dn that Baikal is going to use * when looking for an attribute or filtering. - * + * * @var string */ protected $ldap_bind_dn; @@ -55,12 +54,12 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic /** * LDAP bind password. * Defines the password used by Baikal for binding. - * + * * @var string */ protected $ldap_bind_password; - /** + /** * LDAP dn pattern for binding. * * %u - gets replaced by full username @@ -93,42 +92,40 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic /** * LDAP base path where to search for attributes * and apply filters. - * + * * @var string */ protected $ldap_search_base; /** * LDAP attribute to search for. - * + * * @var string */ protected $ldap_search_attribute; /** * LDAP filter to apply. - * + * * @var string */ protected $ldap_search_filter; /** * LDAP group to check if a user is member of. - * + * * @var string */ protected $ldap_group; /** * Replaces patterns for their assigned value. - * + * * @param string &$base * @param string $username - * */ - protected function patternReplace(&$base, $username) - { + protected function patternReplace(&$base, $username) { $user_split = explode('@', $username, 2); $ldap_user = $user_split[0]; $ldap_domain = ''; @@ -146,16 +143,15 @@ protected function patternReplace(&$base, $username) /** * Checks if a user can bind with a password. * If an error is produced, it will be logged. - * + * * @param \LDAP\Connection &$conn * @param string $dn * @param string $password - * + * * @return bool */ - protected function doesBind(&$conn, $dn, $password) - { + protected function doesBind(&$conn, $dn, $password) { try { $bind = ldap_bind($conn, $dn, $password); if ($bind) @@ -164,6 +160,7 @@ protected function doesBind(&$conn, $dn, $password) error_log($e->getMessage()); error_log(ldap_error($conn)); } + return false; } @@ -174,10 +171,8 @@ protected function doesBind(&$conn, $dn, $password) * @param string $ldap_dn * @param string $ldap_cn * @param string $ldap_mail - * */ - public function __construct(\PDO $pdo, $table_name = 'users', $ldap_mode = 'DN', $ldap_uri = 'ldap://127.0.0.1', $ldap_bind_dn = 'cn=baikal,ou=apps,dc=example,dc=com', $ldap_bind_password = '', $ldap_dn = 'mail=%u', $ldap_cn = 'cn', $ldap_mail = 'mail', $ldap_search_base = 'ou=users,dc=example,dc=com', $ldap_search_attribute = 'uid=%U', $ldap_search_filter = '(objectClass=*)', $ldap_group = 'cn=baikal,ou=groups,dc=example,dc=com') - { + public function __construct(\PDO $pdo, $table_name = 'users', $ldap_mode = 'DN', $ldap_uri = 'ldap://127.0.0.1', $ldap_bind_dn = 'cn=baikal,ou=apps,dc=example,dc=com', $ldap_bind_password = '', $ldap_dn = 'mail=%u', $ldap_cn = 'cn', $ldap_mail = 'mail', $ldap_search_base = 'ou=users,dc=example,dc=com', $ldap_search_attribute = 'uid=%U', $ldap_search_filter = '(objectClass=*)', $ldap_group = 'cn=baikal,ou=groups,dc=example,dc=com') { $this->pdo = $pdo; $this->table_name = $table_name; $this->ldap_mode = $ldap_mode; @@ -201,8 +196,7 @@ public function __construct(\PDO $pdo, $table_name = 'users', $ldap_mode = 'DN', * * @return bool */ - protected function ldapOpen($username, $password) - { + protected function ldapOpen($username, $password) { $conn = ldap_connect($this->ldap_uri); if(!$conn) return false; @@ -224,17 +218,17 @@ protected function ldapOpen($username, $password) $attribute = $this->ldap_search_attribute; $this->patternReplace($attribute, $username); - $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, '('.$attribute.')', [explode('=', $attribute, 2)[0]], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; + $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, '(' . $attribute . ')', [explode('=', $attribute, 2)[0]], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; $dn = $result["dn"]; if ($this->ldap_mode == 'Group') { - $inGroup = FALSE; + $inGroup = false; $members = ldap_get_entries($conn, ldap_read($conn, $this->ldap_group, '(objectClass=*)', ['member', 'uniqueMember'], 0, 0, 0, LDAP_DEREF_NEVER, []))[0]; if (isset($members["member"])) { foreach ($members["member"] as $member) { if ($member == $result["dn"]) { - $inGroup = TRUE; + $inGroup = true; break; } } @@ -242,7 +236,7 @@ protected function ldapOpen($username, $password) if (isset($members["uniqueMember"])) { foreach ($members["uniqueMember"] as $member) { if ($member == $result["dn"]) { - $inGroup = TRUE; + $inGroup = false; break; } } @@ -277,13 +271,13 @@ protected function ldapOpen($username, $password) error_log('Unknown LDAP authentication mode'); } - if($success){ + if ($success){ $stmt = $this->pdo->prepare('SELECT username, digesta1 FROM ' . $this->table_name . ' WHERE username = ?'); $stmt->execute([$username]); $result = $stmt->fetchAll(); if (empty($result)) { - $search_results = ldap_read($conn, $dn, '(objectclass=*)', array($this->ldap_cn, $this->ldap_mail)); + $search_results = ldap_read($conn, $dn, '(objectclass=*)', [$this->ldap_cn, $this->ldap_mail]); $entry = ldap_get_entries($conn, $search_results); $user_displayname = $username; $user_email = 'unset-email'; @@ -313,8 +307,7 @@ protected function ldapOpen($username, $password) * * @return bool */ - protected function validateUserPass($username, $password) - { + protected function validateUserPass($username, $password) { return $this->ldapOpen($username, $password); } } From 9806378ce5cc424fc8444514398c681283304baa Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sun, 24 Jul 2022 15:33:35 +0000 Subject: [PATCH 08/41] Fix Standard.php, according to linter. --- Core/Frameworks/Baikal/Model/Config/Standard.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 734e56d36..da641ddad 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -136,7 +136,6 @@ function formMorphologyForThisModelInstance() { "label" => "The base of the LDAP search", ])); - $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_search_attribute", "label" => "Attribute and match the user with.; with replacments %u => username, %U => user part, %d => domain part of username, %1-9 parts of the domain in reverse order", From 4eb5981b0f6404b00feed57719964942d3b83973 Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sun, 24 Jul 2022 15:34:34 +0000 Subject: [PATCH 09/41] Fix (BaikalAdmin) Standard.php, according to linter. --- Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php b/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php index ea931d865..18a0552f4 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php @@ -99,7 +99,7 @@ function morphologyHook(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { $oMorpho->remove("ldap_dn"); $oMorpho->remove("ldap_search_filter"); } else { - error_log('Unknown LDAP mode: '.$sLDAPm); + error_log('Unknown LDAP mode: ' . $sLDAPm); } } else { $oMorpho->remove("ldap_mode"); From 996ea8df2bf6f57f8e59ef4fd806a4207d8e38fa Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Thu, 28 Jul 2022 08:49:06 +0000 Subject: [PATCH 10/41] Added Curly Braces to if statements. --- Core/Frameworks/Baikal/Core/LDAP.php | 32 +++++++++++++++++----------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 8cefaf04f..1996c5f70 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -124,20 +124,21 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic { * @param string &$base * @param string $username */ - protected function patternReplace(&$base, $username) { $user_split = explode('@', $username, 2); $ldap_user = $user_split[0]; $ldap_domain = ''; - if (count($user_split) > 1) + if (count($user_split) > 1) { $ldap_domain = $user_split[1]; + } $domain_split = array_reverse(explode('.', $ldap_domain)); $base = str_replace('%u', $username, $base); $base = str_replace('%U', $ldap_user, $base); $base = str_replace('%d', $ldap_domain, $base); - for($i = 1; $i <= count($domain_split) and $i <= 9; $i++) + for($i = 1; $i <= count($domain_split) and $i <= 9; $i++) { $base = str_replace('%' . $i, $domain_split[$i - 1], $base); + } } /** @@ -150,17 +151,17 @@ protected function patternReplace(&$base, $username) { * * @return bool */ - protected function doesBind(&$conn, $dn, $password) { try { $bind = ldap_bind($conn, $dn, $password); - if ($bind) + if ($bind) { return true; + } } catch (\ErrorException $e) { error_log($e->getMessage()); error_log(ldap_error($conn)); } - + return false; } @@ -198,10 +199,12 @@ public function __construct(\PDO $pdo, $table_name = 'users', $ldap_mode = 'DN', */ protected function ldapOpen($username, $password) { $conn = ldap_connect($this->ldap_uri); - if(!$conn) + if(!$conn) { return false; - if(!ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3)) + } + if(!ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3)) { return false; + } $success = false; @@ -219,7 +222,7 @@ protected function ldapOpen($username, $password) { $this->patternReplace($attribute, $username); $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, '(' . $attribute . ')', [explode('=', $attribute, 2)[0]], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; - + $dn = $result["dn"]; if ($this->ldap_mode == 'Group') { @@ -241,8 +244,9 @@ protected function ldapOpen($username, $password) { } } } - if (!$inGroup) + if (!$inGroup) { return false; + } } $success = $this->doesBind($conn, $dn, $password); @@ -271,7 +275,7 @@ protected function ldapOpen($username, $password) { error_log('Unknown LDAP authentication mode'); } - if ($success){ + if ($success) { $stmt = $this->pdo->prepare('SELECT username, digesta1 FROM ' . $this->table_name . ' WHERE username = ?'); $stmt->execute([$username]); $result = $stmt->fetchAll(); @@ -281,10 +285,12 @@ protected function ldapOpen($username, $password) { $entry = ldap_get_entries($conn, $search_results); $user_displayname = $username; $user_email = 'unset-email'; - if (!empty($entry[0][$this->ldap_cn])) + if (!empty($entry[0][$this->ldap_cn])) { $user_displayname = $entry[0][$this->ldap_cn][0]; - if (!empty($entry[0][$this->ldap_mail])) + } + if (!empty($entry[0][$this->ldap_mail])) { $user_email = $entry[0][$this->ldap_mail][0]; + } $user = new \Baikal\Model\User(); $user->set('username', $username); From 7d6067f4606767c4b10418239347626ba626616b Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Thu, 28 Jul 2022 08:51:02 +0000 Subject: [PATCH 11/41] Added a couple of missing spaces --- Core/Frameworks/Baikal/Core/LDAP.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 1996c5f70..1936e553d 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -136,7 +136,7 @@ protected function patternReplace(&$base, $username) { $base = str_replace('%u', $username, $base); $base = str_replace('%U', $ldap_user, $base); $base = str_replace('%d', $ldap_domain, $base); - for($i = 1; $i <= count($domain_split) and $i <= 9; $i++) { + for ($i = 1; $i <= count($domain_split) and $i <= 9; ++$i) { $base = str_replace('%' . $i, $domain_split[$i - 1], $base); } } @@ -199,10 +199,10 @@ public function __construct(\PDO $pdo, $table_name = 'users', $ldap_mode = 'DN', */ protected function ldapOpen($username, $password) { $conn = ldap_connect($this->ldap_uri); - if(!$conn) { + if (!$conn) { return false; } - if(!ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3)) { + if (!ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3)) { return false; } From 42a926b73c6980abdef9d02bd178280bfeac9192 Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sat, 30 Jul 2022 13:30:19 +0000 Subject: [PATCH 12/41] Added quotation marks surrounding url, and a period. --- Core/Frameworks/Baikal/Core/LDAP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 1936e553d..b783b6e7c 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -36,7 +36,7 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic { /** * LDAP server uri. - * e.g. ldaps://ldap.example.org + * e.g. "ldaps://ldap.example.org". * * @var string */ From 25741a69c903038966276ba0e0a533a582202b22 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Wed, 7 Sep 2022 16:50:37 +0200 Subject: [PATCH 13/41] Fixed https://github.com/sabre-io/Baikal/pull/1124#issuecomment-1239465040 --- Core/Frameworks/Baikal/Core/LDAP.php | 10 +++-- .../Baikal/Model/Config/Standard.php | 41 ++++++++++--------- .../Controller/Settings/Standard.php | 1 + 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index b783b6e7c..3747361a4 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -2,6 +2,8 @@ namespace Baikal\Core; +use Exception; + /** * This is an authentication backend that uses ldap. * @@ -173,7 +175,7 @@ protected function doesBind(&$conn, $dn, $password) { * @param string $ldap_cn * @param string $ldap_mail */ - public function __construct(\PDO $pdo, $table_name = 'users', $ldap_mode = 'DN', $ldap_uri = 'ldap://127.0.0.1', $ldap_bind_dn = 'cn=baikal,ou=apps,dc=example,dc=com', $ldap_bind_password = '', $ldap_dn = 'mail=%u', $ldap_cn = 'cn', $ldap_mail = 'mail', $ldap_search_base = 'ou=users,dc=example,dc=com', $ldap_search_attribute = 'uid=%U', $ldap_search_filter = '(objectClass=*)', $ldap_group = 'cn=baikal,ou=groups,dc=example,dc=com') { + public function __construct(\PDO $pdo, $table_name, $ldap_mode, $ldap_uri, $ldap_bind_dn, $ldap_bind_password, $ldap_dn, $ldap_cn, $ldap_mail, $ldap_search_base, $ldap_search_attribute, $ldap_search_filter, $ldap_group) { $this->pdo = $pdo; $this->table_name = $table_name; $this->ldap_mode = $ldap_mode; @@ -221,13 +223,15 @@ protected function ldapOpen($username, $password) { $attribute = $this->ldap_search_attribute; $this->patternReplace($attribute, $username); - $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, '(' . $attribute . ')', [explode('=', $attribute, 2)[0]], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; + $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, '(' . $attribute . ')', + [explode('=', $attribute, 2)[0]], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; $dn = $result["dn"]; if ($this->ldap_mode == 'Group') { $inGroup = false; - $members = ldap_get_entries($conn, ldap_read($conn, $this->ldap_group, '(objectClass=*)', ['member', 'uniqueMember'], 0, 0, 0, LDAP_DEREF_NEVER, []))[0]; + $members = ldap_get_entries($conn, ldap_read($conn, $this->ldap_group, '(objectClass=*)', + ['member', 'uniqueMember'], 0, 0, 0, LDAP_DEREF_NEVER, []))[0]; if (isset($members["member"])) { foreach ($members["member"] as $member) { if ($member == $result["dn"]) { diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index da641ddad..7319999d1 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -36,6 +36,7 @@ class Standard extends \Baikal\Model\Config { "timezone" => "Europe/Paris", "card_enabled" => true, "cal_enabled" => true, + "admin_passwordhash" => "", "dav_auth_type" => "Digest", "ldap_mode" => "None", "ldap_uri" => "ldap://127.0.0.1", @@ -48,7 +49,6 @@ class Standard extends \Baikal\Model\Config { "ldap_search_attribute" => "uid=%U", "ldap_search_filter" => "(objectClass=*)", "ldap_group" => "cn=baikal,ou=groups,dc=example,dc=com", - "admin_passwordhash" => "", "failed_access_message" => "user %u authentication failure for Baikal", // While not editable as will change admin & any existing user passwords, // could be set to different value when migrating from legacy config @@ -81,12 +81,24 @@ function formMorphologyForThisModelInstance() { "label" => "Enable CalDAV", ])); + $oMorpho->add(new \Formal\Element\Text([ "prop" => "invite_from", "label" => "Email invite sender address", "help" => "Leave empty to disable sending invite emails", ])); + $oMorpho->add(new \Formal\Element\Password([ + "prop" => "admin_passwordhash", + "label" => "Admin password", + ])); + + $oMorpho->add(new \Formal\Element\Password([ + "prop" => "admin_passwordhash_confirm", + "label" => "Admin password, confirmation", + "validation" => "sameas:admin_passwordhash", + ])); + $oMorpho->add(new \Formal\Element\Listbox([ "prop" => "dav_auth_type", "label" => "WebDAV authentication type", @@ -103,7 +115,7 @@ function formMorphologyForThisModelInstance() { $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_uri", - "label" => "URI of the LDAP server; default ldap://127.0.0.1", + "label" => "URI of the LDAP server", ])); $oMorpho->add(new \Formal\Element\Text([ @@ -118,17 +130,18 @@ function formMorphologyForThisModelInstance() { $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_dn", - "label" => "User DN for bind; with replacments %u => username, %U => user part, %d => domain part of username, %1-9 parts of the domain in reverse order", + "label" => "User DN for bind", + "help" => "Replacments: %u => username, %U => user part, %d => domain part of username, %1-9 parts of the domain in reverse order", ])); $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_cn", - "label" => "LDAP-attribute for displayname; default cn", + "label" => "LDAP-attribute for displayname", ])); $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_mail", - "label" => "LDAP-attribute for email; default mail", + "label" => "LDAP-attribute for email", ])); $oMorpho->add(new \Formal\Element\Text([ @@ -138,28 +151,18 @@ function formMorphologyForThisModelInstance() { $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_search_attribute", - "label" => "Attribute and match the user with.; with replacments %u => username, %U => user part, %d => domain part of username, %1-9 parts of the domain in reverse order", + "label" => "Attribute and match the user with", + "help" => "Replacments: %u => username, %U => user part, %d => domain part of username, %1-9 parts of the domain in reverse order", ])); $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_search_filter", - "label" => "The LDAP filter to be applied to the search.", + "label" => "The LDAP filter to be applied to the search", ])); $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_group", - "label" => "The Group DN that contains the member atribute of the user.", - ])); - - $oMorpho->add(new \Formal\Element\Password([ - "prop" => "admin_passwordhash", - "label" => "Admin password", - ])); - - $oMorpho->add(new \Formal\Element\Password([ - "prop" => "admin_passwordhash_confirm", - "label" => "Admin password, confirmation", - "validation" => "sameas:admin_passwordhash", + "label" => "The Group DN that contains the member atribute of the user", ])); try { diff --git a/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php b/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php index 18a0552f4..1473c488f 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Settings/Standard.php @@ -102,6 +102,7 @@ function morphologyHook(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { error_log('Unknown LDAP mode: ' . $sLDAPm); } } else { + $oMorpho->remove("ldap_uri"); $oMorpho->remove("ldap_mode"); $oMorpho->remove("ldap_bind_dn"); $oMorpho->remove("ldap_bind_password"); From d9c9d3dc28c9e3b8f3f23a4bc30d3b0c75b4aaff Mon Sep 17 00:00:00 2001 From: El-Virus Date: Wed, 7 Sep 2022 16:53:45 +0200 Subject: [PATCH 14/41] Fixed linter errors --- Core/Frameworks/Baikal/Core/LDAP.php | 6 ++---- Core/Frameworks/Baikal/Model/Config/Standard.php | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 3747361a4..e9edc02b1 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -2,8 +2,6 @@ namespace Baikal\Core; -use Exception; - /** * This is an authentication backend that uses ldap. * @@ -223,14 +221,14 @@ protected function ldapOpen($username, $password) { $attribute = $this->ldap_search_attribute; $this->patternReplace($attribute, $username); - $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, '(' . $attribute . ')', + $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, '(' . $attribute . ')', [explode('=', $attribute, 2)[0]], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; $dn = $result["dn"]; if ($this->ldap_mode == 'Group') { $inGroup = false; - $members = ldap_get_entries($conn, ldap_read($conn, $this->ldap_group, '(objectClass=*)', + $members = ldap_get_entries($conn, ldap_read($conn, $this->ldap_group, '(objectClass=*)', ['member', 'uniqueMember'], 0, 0, 0, LDAP_DEREF_NEVER, []))[0]; if (isset($members["member"])) { foreach ($members["member"] as $member) { diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 7319999d1..98b80a2b9 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -81,7 +81,6 @@ function formMorphologyForThisModelInstance() { "label" => "Enable CalDAV", ])); - $oMorpho->add(new \Formal\Element\Text([ "prop" => "invite_from", "label" => "Email invite sender address", From 7fda407cfb44c01bdf90199ab590963bf4b2b4cd Mon Sep 17 00:00:00 2001 From: Aisha Tammy Date: Wed, 7 Sep 2022 15:25:07 +0000 Subject: [PATCH 15/41] fix the patternReplace function Signed-off-by: Aisha Tammy --- Core/Frameworks/Baikal/Core/LDAP.php | 93 ++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 26 deletions(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index e9edc02b1..ebc77220f 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -62,15 +62,6 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic { /** * LDAP dn pattern for binding. * - * %u - gets replaced by full username - * %U - gets replaced by user part when the - * username is an email address - * %d - gets replaced by domain part when the - * username is an email address - * %1-9 - gets replaced by parts of the the domain - * split by '.' in reverse order - * mail.example.org: %1 = org, %2 = example, %3 = mail - * * @var string */ protected $ldap_dn; @@ -119,26 +110,76 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic { protected $ldap_group; /** - * Replaces patterns for their assigned value. + * Replaces patterns for their assigned value using the + * given username, using cyrus-sasl style replacements. * - * @param string &$base + * %u - gets replaced by full username + * %U - gets replaced by user part when the + * username is an email address + * %d - gets replaced by domain part when the + * username is an email address + * %% - gets replaced by % + * %1-9 - gets replaced by parts of the the domain + * split by '.' in reverse order + * + * full example for jane.doe@mail.example.org: + * %u = jane.doe@mail.example.org + * %U = jane.doe + * %d = mail.example.org + * %1 = org + * %2 = example + * %3 = mail + * + * @param string $line * @param string $username + * + * @return string */ - protected function patternReplace(&$base, $username) { - $user_split = explode('@', $username, 2); - $ldap_user = $user_split[0]; - $ldap_domain = ''; - if (count($user_split) > 1) { - $ldap_domain = $user_split[1]; + protected function patternReplace($line, $username) { + $user_split = [$username]; + $user = $username; + $domain = ''; + try { + $user_split = explode('@', $username, 2); + $user = $user_split[0]; + if (2 == count($user_split)) { + $domain = $user_split[1]; + } + } catch (Exception $ignored) { + } + $domain_split = []; + try { + $domain_split = array_reverse(explode('.', $domain)); + } catch (Exception $ignored) { + $domain_split = []; } - $domain_split = array_reverse(explode('.', $ldap_domain)); - $base = str_replace('%u', $username, $base); - $base = str_replace('%U', $ldap_user, $base); - $base = str_replace('%d', $ldap_domain, $base); - for ($i = 1; $i <= count($domain_split) and $i <= 9; ++$i) { - $base = str_replace('%' . $i, $domain_split[$i - 1], $base); + $parsed_line = ''; + for ($i = 0; $i < strlen($line); ++$i) { + if ('%' == $line[$i]) { + ++$i; + $next_char = $line[$i]; + if ('u' == $next_char) { + $parsed_line .= $username; + } elseif ('U' == $next_char) { + $parsed_line .= $user; + } elseif ('d' == $next_char) { + $parsed_line .= $domain; + } elseif ('%' == $next_char) { + $parsed_line .= '%'; + } else { + for ($j = 1; $j <= count($domain_split) && $j <= 9; ++$j) { + if ($next_char == ''.$j) { + $parsed_line .= $domain_split[$j - 1]; + } + } + } + } else { + $parsed_line .= $line[$i]; + } } + + return $parsed_line; } /** @@ -209,7 +250,7 @@ protected function ldapOpen($username, $password) { $success = false; if ($this->ldap_mode == 'DN') { - $this->patternReplace($dn, $username); + $dn = $this->patternReplace($dn, $username); $success = $this->doesBind($conn, $dn, $password); } elseif ($this->ldap_mode == 'Attribute' || $this->ldap_mode == 'Group') { @@ -219,7 +260,7 @@ protected function ldapOpen($username, $password) { } $attribute = $this->ldap_search_attribute; - $this->patternReplace($attribute, $username); + $attribute = $this->patternReplace($attribute, $username); $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, '(' . $attribute . ')', [explode('=', $attribute, 2)[0]], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; @@ -263,7 +304,7 @@ protected function ldapOpen($username, $password) { } $filter = $this->ldap_search_filter; - $this->patternReplace($filter, $username); + $filter = $this->patternReplace($filter, $username); $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, $filter, [], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; From 34bc4a9c6552598557aa8d4473614715a31fd5f1 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Thu, 8 Sep 2022 20:03:14 +0200 Subject: [PATCH 16/41] LDAP bind Password hidden --- Core/Frameworks/Baikal/Model/Config/Standard.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 98b80a2b9..19757829d 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -177,6 +177,7 @@ function formMorphologyForThisModelInstance() { $sNotice = "-- Leave empty to keep current password --"; $oMorpho->element("admin_passwordhash")->setOption("placeholder", $sNotice); $oMorpho->element("admin_passwordhash_confirm")->setOption("placeholder", $sNotice); + $oMorpho->element("ldap_bind_password")->setOption("placeholder", "-- Not Shown --"); } return $oMorpho; @@ -187,7 +188,7 @@ function label() { } function set($sProp, $sValue) { - if ($sProp === "admin_passwordhash" || $sProp === "admin_passwordhash_confirm") { + if ($sProp === "admin_passwordhash" || $sProp === "admin_passwordhash_confirm" || $sProp == "ldap_bind_password") { # Special handling for password and passwordconfirm if ($sProp === "admin_passwordhash" && $sValue !== "") { @@ -204,7 +205,7 @@ function set($sProp, $sValue) { } function get($sProp) { - if ($sProp === "admin_passwordhash" || $sProp === "admin_passwordhash_confirm") { + if ($sProp === "admin_passwordhash" || $sProp === "admin_passwordhash_confirm" || $sProp === "ldap_bind_password") { return ""; } From 3e6ab43e7b26c8b23ec94f644d8fbaf214df29a4 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Thu, 8 Sep 2022 20:12:18 +0200 Subject: [PATCH 17/41] Fix LDAP.php, according to linter. --- Core/Frameworks/Baikal/Core/LDAP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index ebc77220f..6bb3fbcdb 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -169,7 +169,7 @@ protected function patternReplace($line, $username) { $parsed_line .= '%'; } else { for ($j = 1; $j <= count($domain_split) && $j <= 9; ++$j) { - if ($next_char == ''.$j) { + if ($next_char == '' . $j) { $parsed_line .= $domain_split[$j - 1]; } } From 38ca1f01388bf351f00d51d8d014179fd6a9641d Mon Sep 17 00:00:00 2001 From: El-Virus Date: Thu, 8 Sep 2022 20:33:44 +0200 Subject: [PATCH 18/41] Epsilon0's merge fix --- Core/Frameworks/Baikal/Core/LDAP.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 6bb3fbcdb..869c413d4 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -2,6 +2,8 @@ namespace Baikal\Core; +use Exception; + /** * This is an authentication backend that uses ldap. * @@ -250,9 +252,9 @@ protected function ldapOpen($username, $password) { $success = false; if ($this->ldap_mode == 'DN') { - $dn = $this->patternReplace($dn, $username); + $dn = $this->patternReplace($this->ldap_dn, $username); - $success = $this->doesBind($conn, $dn, $password); + $success = $this->doesBind($conn, $username, $password); } elseif ($this->ldap_mode == 'Attribute' || $this->ldap_mode == 'Group') { try { if (!$this->doesBind($conn, $this->ldap_bind_dn, $this->ldap_bind_password)) { From 3d3e7568cf387e4ef50080f5a1fb8caff5858992 Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sat, 15 Oct 2022 10:37:27 +0000 Subject: [PATCH 19/41] Actually allow LDAP bind password to be set As pointed out by ByteHamster, in https://github.com/sabre-io/Baikal/pull/1124#discussion_r996282661 --- Core/Frameworks/Baikal/Model/Config/Standard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 19757829d..7e0d9763c 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -188,7 +188,7 @@ function label() { } function set($sProp, $sValue) { - if ($sProp === "admin_passwordhash" || $sProp === "admin_passwordhash_confirm" || $sProp == "ldap_bind_password") { + if ($sProp === "admin_passwordhash" || $sProp === "admin_passwordhash_confirm") { # Special handling for password and passwordconfirm if ($sProp === "admin_passwordhash" && $sValue !== "") { From 6e455ebeee66da1343036a65cd3409405034a0d0 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Sun, 16 Oct 2022 16:28:03 +0200 Subject: [PATCH 20/41] Added LDAP Config Struct and default LDAP Params to dist config. --- Core/Frameworks/Baikal/Core/LDAP.php | 147 +++++-------------------- Core/Frameworks/Baikal/Core/Server.php | 14 ++- config/baikal.yaml.dist | 11 ++ 3 files changed, 54 insertions(+), 118 deletions(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 869c413d4..eacf19c14 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -28,88 +28,12 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic { protected $table_name; /** - * LDAP mode. - * Defines if LDAP authentication should match - * by DN, Attribute, or Filter. - * - * @var string - */ - protected $ldap_mode; - - /** - * LDAP server uri. - * e.g. "ldaps://ldap.example.org". - * - * @var string - */ - protected $ldap_uri; - - /** - * LDAP bind dn. - * Defines the bind dn that Baikal is going to use - * when looking for an attribute or filtering. - * - * @var string - */ - protected $ldap_bind_dn; - - /** - * LDAP bind password. - * Defines the password used by Baikal for binding. - * - * @var string - */ - protected $ldap_bind_password; - - /** - * LDAP dn pattern for binding. - * - * @var string - */ - protected $ldap_dn; - - /** - * LDAP attribute to use for name. - * - * @var string - */ - protected $ldap_cn; - - /** - * LDAP attribute used for mail. - * - * @var string - */ - protected $ldap_mail; - - /** - * LDAP base path where to search for attributes - * and apply filters. - * - * @var string - */ - protected $ldap_search_base; - - /** - * LDAP attribute to search for. - * - * @var string - */ - protected $ldap_search_attribute; - - /** - * LDAP filter to apply. - * - * @var string - */ - protected $ldap_search_filter; - - /** - * LDAP group to check if a user is member of. - * - * @var string + * LDAP Config. + * LDAP Config Struct. + * + * @var \Baikal\Model\Structs\LDAPConfig */ - protected $ldap_group; + protected $ldap_config; /** * Replaces patterns for their assigned value using the @@ -211,25 +135,14 @@ protected function doesBind(&$conn, $dn, $password) { /** * Creates the backend object. * - * @param string $ldap_uri - * @param string $ldap_dn - * @param string $ldap_cn - * @param string $ldap_mail + * @param \PD0 $pdo + * @param string $table_name + * @param \Baikal\Model\Structs\LDAPConfig $ldap_config */ - public function __construct(\PDO $pdo, $table_name, $ldap_mode, $ldap_uri, $ldap_bind_dn, $ldap_bind_password, $ldap_dn, $ldap_cn, $ldap_mail, $ldap_search_base, $ldap_search_attribute, $ldap_search_filter, $ldap_group) { - $this->pdo = $pdo; - $this->table_name = $table_name; - $this->ldap_mode = $ldap_mode; - $this->ldap_uri = $ldap_uri; - $this->ldap_bind_dn = $ldap_bind_dn; - $this->ldap_bind_password = $ldap_bind_password; - $this->ldap_dn = $ldap_dn; - $this->ldap_cn = $ldap_cn; - $this->ldap_mail = $ldap_mail; - $this->ldap_search_base = $ldap_search_base; - $this->ldap_search_attribute = $ldap_search_attribute; - $this->ldap_search_filter = $ldap_search_filter; - $this->ldap_group = $ldap_group; + public function __construct(\PDO $pdo, $table_name, $ldap_config) { + $this->pdo = $pdo; + $this->table_name = $table_name; + $this->ldap_config = $ldap_config; } /** @@ -241,7 +154,7 @@ public function __construct(\PDO $pdo, $table_name, $ldap_mode, $ldap_uri, $ldap * @return bool */ protected function ldapOpen($username, $password) { - $conn = ldap_connect($this->ldap_uri); + $conn = ldap_connect($this->ldap_config->ldap_uri); if (!$conn) { return false; } @@ -251,27 +164,27 @@ protected function ldapOpen($username, $password) { $success = false; - if ($this->ldap_mode == 'DN') { - $dn = $this->patternReplace($this->ldap_dn, $username); + if ($this->ldap_config->ldap_mode == 'DN') { + $dn = $this->patternReplace($this->ldap_config->ldap_dn, $username); $success = $this->doesBind($conn, $username, $password); - } elseif ($this->ldap_mode == 'Attribute' || $this->ldap_mode == 'Group') { + } elseif ($this->ldap_config->ldap_mode == 'Attribute' || $this->ldap_config->ldap_mode == 'Group') { try { - if (!$this->doesBind($conn, $this->ldap_bind_dn, $this->ldap_bind_password)) { + if (!$this->doesBind($conn, $this->ldap_config->ldap_bind_dn, $this->ldap_config->ldap_bind_password)) { return false; } - $attribute = $this->ldap_search_attribute; + $attribute = $this->ldap_config->ldap_search_attribute; $attribute = $this->patternReplace($attribute, $username); - $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, '(' . $attribute . ')', + $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_config->ldap_search_base, '(' . $attribute . ')', [explode('=', $attribute, 2)[0]], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; $dn = $result["dn"]; - if ($this->ldap_mode == 'Group') { + if ($this->ldap_config->ldap_mode == 'Group') { $inGroup = false; - $members = ldap_get_entries($conn, ldap_read($conn, $this->ldap_group, '(objectClass=*)', + $members = ldap_get_entries($conn, ldap_read($conn, $this->ldap_config->ldap_group, '(objectClass=*)', ['member', 'uniqueMember'], 0, 0, 0, LDAP_DEREF_NEVER, []))[0]; if (isset($members["member"])) { foreach ($members["member"] as $member) { @@ -299,16 +212,16 @@ protected function ldapOpen($username, $password) { error_log($e->getMessage()); error_log(ldap_error($conn)); } - } elseif ($this->ldap_mode == 'Filter') { + } elseif ($this->ldap_config->ldap_mode == 'Filter') { try { - if (!$this->doesBind($conn, $this->ldap_bind_dn, $this->ldap_bind_password)) { + if (!$this->doesBind($conn, $this->ldap_config->ldap_bind_dn, $this->ldap_config->ldap_bind_password)) { return false; } - $filter = $this->ldap_search_filter; + $filter = $this->ldap_config->ldap_search_filter; $filter = $this->patternReplace($filter, $username); - $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_search_base, $filter, [], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; + $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_config->ldap_search_base, $filter, [], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; $dn = $result["dn"]; $success = $this->doesBind($conn, $dn, $password); @@ -326,15 +239,15 @@ protected function ldapOpen($username, $password) { $result = $stmt->fetchAll(); if (empty($result)) { - $search_results = ldap_read($conn, $dn, '(objectclass=*)', [$this->ldap_cn, $this->ldap_mail]); + $search_results = ldap_read($conn, $dn, '(objectclass=*)', [$this->ldap_config->ldap_cn, $this->ldap_config->ldap_mail]); $entry = ldap_get_entries($conn, $search_results); $user_displayname = $username; $user_email = 'unset-email'; - if (!empty($entry[0][$this->ldap_cn])) { - $user_displayname = $entry[0][$this->ldap_cn][0]; + if (!empty($entry[0][$this->ldap_config->ldap_cn])) { + $user_displayname = $entry[0][$this->ldap_config->ldap_cn][0]; } - if (!empty($entry[0][$this->ldap_mail])) { - $user_email = $entry[0][$this->ldap_mail][0]; + if (!empty($entry[0][$this->ldap_config->ldap_mail])) { + $user_email = $entry[0][$this->ldap_config->ldap_mail][0]; } $user = new \Baikal\Model\User(); diff --git a/Core/Frameworks/Baikal/Core/Server.php b/Core/Frameworks/Baikal/Core/Server.php index 0d7bd28c4..b077f07b7 100644 --- a/Core/Frameworks/Baikal/Core/Server.php +++ b/Core/Frameworks/Baikal/Core/Server.php @@ -136,7 +136,19 @@ protected function initServer() { } elseif ($this->authType === 'Apache') { $authBackend = new \Sabre\DAV\Auth\Backend\Apache(); } elseif ($this->authType === 'LDAP') { - $authBackend = new \Baikal\Core\LDAP($this->pdo, 'users', $config['system']['ldap_mode'], $config['system']['ldap_uri'], $config['system']['ldap_bind_dn'], $config['system']['ldap_bind_password'], $config['system']['ldap_dn'], $config['system']['ldap_cn'], $config['system']['ldap_mail'], $config['system']['ldap_search_base'], $config['system']['ldap_search_attribute'], $config['system']['ldap_search_filter'], $config['system']['ldap_group']); + $LDAPConfig = new \Baikal\Model\Structs\LDAPConfig(); + $LDAPConfig->ldap_mode = $config['system']['ldap_mode']; + $LDAPConfig->ldap_uri = $config['system']['ldap_uri']; + $LDAPConfig->ldap_bind_dn = $config['system']['ldap_bind_dn']; + $LDAPConfig->ldap_bind_password = $config['system']['ldap_bind_password']; + $LDAPConfig->ldap_dn = $config['system']['ldap_dn']; + $LDAPConfig->ldap_cn = $config['system']['ldap_cn']; + $LDAPConfig->ldap_mail = $config['system']['ldap_mail']; + $LDAPConfig->ldap_search_base = $config['system']['ldap_search_base']; + $LDAPConfig->ldap_search_attribute = $config['system']['ldap_search_attribute']; + $LDAPConfig->ldap_search_filter = $config['system']['ldap_search_filter']; + $LDAPConfig->ldap_group = $config['system']['ldap_group']; + $authBackend = new \Baikal\Core\LDAP($this->pdo, 'users', $LDAPConfig); } else { $authBackend = new \Sabre\DAV\Auth\Backend\PDO($this->pdo); $authBackend->setRealm($this->authRealm); diff --git a/config/baikal.yaml.dist b/config/baikal.yaml.dist index 3fd772b59..a60113445 100644 --- a/config/baikal.yaml.dist +++ b/config/baikal.yaml.dist @@ -9,6 +9,17 @@ system: failed_access_message: 'user %u authentication failure for Baikal' auth_realm: BaikalDAV base_uri: '' + ldap_mode: 'None' + ldap_uri: 'ldap://127.0.0.1' + ldap_bind_dn: 'cn=baikal,ou=apps,dc=example,dc=com' + ldap_bind_password: '' + ldap_dn: 'mail=%u' + ldap_cn: 'cn' + ldap_mail: 'mail' + ldap_search_base: 'ou=users,dc=example,dc=com' + ldap_search_attribute: 'uid=%U' + ldap_search_filter: '(objectClass=*)' + ldap_group: 'cn=baikal,ou=groups,dc=example,dc=com' database: encryption_key: 5d3f0fa0192e3058ea70f1bb20924add sqlite_file: "absolute/path/to/Specific/db/db.sqlite" From 9a175df67f1bbed735b5e08c186ecec75610d6b8 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Sun, 16 Oct 2022 16:29:34 +0200 Subject: [PATCH 21/41] Fix LDAP.php, according to linter. --- Core/Frameworks/Baikal/Core/LDAP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index eacf19c14..b24281139 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -30,7 +30,7 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic { /** * LDAP Config. * LDAP Config Struct. - * + * * @var \Baikal\Model\Structs\LDAPConfig */ protected $ldap_config; From 0bfa4a849429ec8a2699ad27d4cb71009ea0eacd Mon Sep 17 00:00:00 2001 From: El-Virus Date: Sun, 16 Oct 2022 16:32:25 +0200 Subject: [PATCH 22/41] Moved Structs folder to correct location. --- .../Baikal/Model/Structs/LDAPConfig.php | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php diff --git a/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php b/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php new file mode 100644 index 000000000..9f04c0c23 --- /dev/null +++ b/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php @@ -0,0 +1,23 @@ + + * @license http://sabre.io/license/ Modified BSD License + */ +class LDAPConfig { + public $ldap_mode; + public $ldap_uri; + public $ldap_bind_dn; + public $ldap_bind_password; + public $ldap_dn; + public $ldap_cn; + public $ldap_mail; + public $ldap_search_base; + public $ldap_search_attribute; + public $ldap_search_filter; + public $ldap_group; +} \ No newline at end of file From 8633f78f68474960f06ce01d4fff756164adbf88 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Sun, 16 Oct 2022 16:33:32 +0200 Subject: [PATCH 23/41] Fix LDAPConfig.php, according to linter. --- Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php b/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php index 9f04c0c23..055d04a86 100644 --- a/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php +++ b/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php @@ -20,4 +20,4 @@ class LDAPConfig { public $ldap_search_attribute; public $ldap_search_filter; public $ldap_group; -} \ No newline at end of file +} From b7d68b3cb2aa72b8f710674b0e7a6a2c4246f9c1 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Mon, 17 Oct 2022 20:04:52 +0200 Subject: [PATCH 24/41] Added empty value on config set safeguard. --- Core/Frameworks/Baikal/Model/Config/Standard.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 7e0d9763c..17ec9e61f 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -201,6 +201,10 @@ function set($sProp, $sValue) { return $this; } + if (!isset($sValue)) { + return; + } + parent::set($sProp, $sValue); } From bf0288f48796a477c41a9e9f8953810016bc5311 Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sun, 30 Oct 2022 12:24:17 +0000 Subject: [PATCH 25/41] Fix LDAP.php's license --- Core/Frameworks/Baikal/Core/LDAP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index b24281139..99975525d 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -10,7 +10,7 @@ * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Aisha Tammy * @author El-Virus - * @license http://sabre.io/license/ Modified BSD License + * @license http://sabre.io/license/ Modified GPL License */ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic { /** From 4fb83972885bbcd908c039080542890fd82bbd96 Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sun, 30 Oct 2022 12:24:38 +0000 Subject: [PATCH 26/41] Fix LDAPConfig.php's license --- Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php b/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php index 055d04a86..ab7e58ced 100644 --- a/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php +++ b/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php @@ -6,7 +6,7 @@ * Struct that holds the Configuration parameters for LDAP authentication. * * @author El-Virus - * @license http://sabre.io/license/ Modified BSD License + * @license http://sabre.io/license/ Modified GPL License */ class LDAPConfig { public $ldap_mode; From 171dab00fdce9180bed5bf7857ed7513ddc0c6e5 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Fri, 4 Nov 2022 15:23:30 +0100 Subject: [PATCH 27/41] Changed copyright notice. Added check for empty bind password. --- Core/Frameworks/Baikal/Core/LDAP.php | 31 ++++++++++++++++--- .../Baikal/Model/Config/Standard.php | 4 +++ .../Baikal/Model/Structs/LDAPConfig.php | 28 +++++++++++++++-- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 99975525d..3227ba357 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -4,13 +4,34 @@ use Exception; +################################################################# +# Copyright notice +# +# (c) 2022 Aisha Tammy +# (c) 2022 El-Virus +# All rights reserved +# +# http://sabre.io/baikal +# +# This script is part of the Baïkal Server project. The Baïkal +# Server project is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# This copyright notice MUST APPEAR in all copies of the script! +################################################################# + /** * This is an authentication backend that uses ldap. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Aisha Tammy - * @author El-Virus - * @license http://sabre.io/license/ Modified GPL License */ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic { /** diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 17ec9e61f..bc87176e5 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -201,6 +201,10 @@ function set($sProp, $sValue) { return $this; } + if ($sProp === "ldap_bind_password" && $sValue === "") { + return; + } + if (!isset($sValue)) { return; } diff --git a/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php b/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php index ab7e58ced..c50995391 100644 --- a/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php +++ b/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php @@ -1,12 +1,34 @@ +# All rights reserved +# +# http://sabre.io/baikal +# +# This script is part of the Baïkal Server project. The Baïkal +# Server project is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# This copyright notice MUST APPEAR in all copies of the script! +################################################################# + namespace Baikal\Model\Structs; /** * Struct that holds the Configuration parameters for LDAP authentication. - * - * @author El-Virus - * @license http://sabre.io/license/ Modified GPL License */ class LDAPConfig { public $ldap_mode; From 8885a9f0de36e0b07c87180abf1cb87869c76324 Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sun, 20 Nov 2022 10:25:46 +0000 Subject: [PATCH 28/41] Changed $username to $dn --- Core/Frameworks/Baikal/Core/LDAP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 3227ba357..e6432011b 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -188,7 +188,7 @@ protected function ldapOpen($username, $password) { if ($this->ldap_config->ldap_mode == 'DN') { $dn = $this->patternReplace($this->ldap_config->ldap_dn, $username); - $success = $this->doesBind($conn, $username, $password); + $success = $this->doesBind($conn, $dn, $password); } elseif ($this->ldap_config->ldap_mode == 'Attribute' || $this->ldap_config->ldap_mode == 'Group') { try { if (!$this->doesBind($conn, $this->ldap_config->ldap_bind_dn, $this->ldap_config->ldap_bind_password)) { From 3f4c6a75ab396587bb051e3c7d2f010037a2035c Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sat, 31 Dec 2022 09:58:06 +0000 Subject: [PATCH 29/41] Removed an article from a settings label Co-authored-by: ByteHamster --- Core/Frameworks/Baikal/Model/Config/Standard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index bc87176e5..4617d2925 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -124,7 +124,7 @@ function formMorphologyForThisModelInstance() { $oMorpho->add(new \Formal\Element\Password([ "prop" => "ldap_bind_password", - "label" => "The password of the bind DN user", + "label" => "Password of the bind DN user", ])); $oMorpho->add(new \Formal\Element\Text([ From 85835531d5c134410139201a8f301aeb90508483 Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sat, 31 Dec 2022 09:58:36 +0000 Subject: [PATCH 30/41] Removed an article from a settings label Co-authored-by: ByteHamster --- Core/Frameworks/Baikal/Model/Config/Standard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 4617d2925..96eac7abb 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -156,7 +156,7 @@ function formMorphologyForThisModelInstance() { $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_search_filter", - "label" => "The LDAP filter to be applied to the search", + "label" => "LDAP filter to be applied to the search", ])); $oMorpho->add(new \Formal\Element\Text([ From afb5d382f5b43c6b1c9505ee6abe477af09f3163 Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sat, 31 Dec 2022 10:07:14 +0000 Subject: [PATCH 31/41] Added slash to ldap_connect --- Core/Frameworks/Baikal/Core/LDAP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index e6432011b..3ff7cdbcd 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -175,7 +175,7 @@ public function __construct(\PDO $pdo, $table_name, $ldap_config) { * @return bool */ protected function ldapOpen($username, $password) { - $conn = ldap_connect($this->ldap_config->ldap_uri); + $conn = \ldap_connect($this->ldap_config->ldap_uri); if (!$conn) { return false; } From de8e4ff2b40da67f9f5ae3e5177047146ae556f0 Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sat, 31 Dec 2022 10:09:12 +0000 Subject: [PATCH 32/41] Fixed typo in settings Co-authored-by: ByteHamster --- Core/Frameworks/Baikal/Model/Config/Standard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 96eac7abb..60fd5f0b7 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -150,7 +150,7 @@ function formMorphologyForThisModelInstance() { $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_search_attribute", - "label" => "Attribute and match the user with", + "label" => "Attribute to match the user with", "help" => "Replacments: %u => username, %U => user part, %d => domain part of username, %1-9 parts of the domain in reverse order", ])); From 12b4121aed0bd4cb82962816ba97e9392bc824eb Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sat, 31 Dec 2022 10:10:38 +0000 Subject: [PATCH 33/41] Remove articles from config page. --- Core/Frameworks/Baikal/Model/Config/Standard.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Frameworks/Baikal/Model/Config/Standard.php b/Core/Frameworks/Baikal/Model/Config/Standard.php index 60fd5f0b7..80028073d 100644 --- a/Core/Frameworks/Baikal/Model/Config/Standard.php +++ b/Core/Frameworks/Baikal/Model/Config/Standard.php @@ -145,7 +145,7 @@ function formMorphologyForThisModelInstance() { $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_search_base", - "label" => "The base of the LDAP search", + "label" => "Base of the LDAP search", ])); $oMorpho->add(new \Formal\Element\Text([ @@ -161,7 +161,7 @@ function formMorphologyForThisModelInstance() { $oMorpho->add(new \Formal\Element\Text([ "prop" => "ldap_group", - "label" => "The Group DN that contains the member atribute of the user", + "label" => "Group DN that contains the member atribute of the user", ])); try { From b492d703eb39348038906f697d4393b31e625ef4 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Fri, 13 Jan 2023 15:35:25 +0100 Subject: [PATCH 34/41] Fixed 'Undefined array key 0' on incorrect username --- Core/Frameworks/Baikal/Core/LDAP.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 3ff7cdbcd..1b7fabff1 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -201,6 +201,10 @@ protected function ldapOpen($username, $password) { $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_config->ldap_search_base, '(' . $attribute . ')', [explode('=', $attribute, 2)[0]], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; + if ((!isset($result)) || (!isset($result["dn"]))) { + return false; + } + $dn = $result["dn"]; if ($this->ldap_config->ldap_mode == 'Group') { From cd967f13d761e3d1dc631ef4a86576fbbd0f1790 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Fri, 13 Jan 2023 15:45:46 +0100 Subject: [PATCH 35/41] Added check for LDAP extension availability --- Core/Frameworks/Baikal/Core/LDAP.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 1b7fabff1..00a15f556 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -2,6 +2,7 @@ namespace Baikal\Core; +use Error; use Exception; ################################################################# @@ -297,6 +298,10 @@ protected function ldapOpen($username, $password) { * @return bool */ protected function validateUserPass($username, $password) { + if (!extension_loaded("ldap")) { + error_log('PHP LDAP extension not enabled'); + return false; + } return $this->ldapOpen($username, $password); } } From 4b3213aea5ac81470084e4959e80f98c55d5e705 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Fri, 13 Jan 2023 15:47:37 +0100 Subject: [PATCH 36/41] Fix LDAP.php, according to linter --- Core/Frameworks/Baikal/Core/LDAP.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 00a15f556..984e7b875 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -300,8 +300,10 @@ protected function ldapOpen($username, $password) { protected function validateUserPass($username, $password) { if (!extension_loaded("ldap")) { error_log('PHP LDAP extension not enabled'); + return false; } + return $this->ldapOpen($username, $password); } } From d62f2713197fa433b2c04156d73c1ecfda5c096c Mon Sep 17 00:00:00 2001 From: El-Virus Date: Fri, 23 Jun 2023 13:34:15 +0200 Subject: [PATCH 37/41] Applied standard settings morphology hook to initialization wizard. --- Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php b/Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php index 66c7c4503..ca00676ec 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php @@ -62,6 +62,7 @@ function execute() { $this->oForm = $this->oModel->formForThisModelInstance([ "close" => false, + "hook.morphology" => [new \BaikalAdmin\Controller\Settings\Standard(), "morphologyHook"], ]); if ($this->oForm->submitted()) { From 1ca48d6bbf2368d8eff6575a18df5eda6cc5ff6e Mon Sep 17 00:00:00 2001 From: El-Virus Date: Sun, 19 Jan 2025 19:06:47 +0100 Subject: [PATCH 38/41] Implemented user federation --- Core/Frameworks/Baikal/Core/LDAP.php | 116 ++--------- Core/Frameworks/Baikal/Core/Server.php | 15 +- .../DBPrincipal.php} | 5 +- .../Baikal/Model/Principal/LDAP.php | 181 ++++++++++++++++++ .../Baikal/Model/Structs/LDAPConfig.php | 22 ++- Core/Frameworks/Baikal/Model/User.php | 23 ++- .../BaikalAdmin/Controller/Users.php | 12 +- .../Resources/Templates/Users.html | 5 + Core/Resources/Db/MySQL/db.sql | 1 + Core/Resources/Db/SQLite/db.sql | 1 + 10 files changed, 261 insertions(+), 120 deletions(-) rename Core/Frameworks/Baikal/Model/{Principal.php => Principal/DBPrincipal.php} (91%) create mode 100644 Core/Frameworks/Baikal/Model/Principal/LDAP.php diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 984e7b875..4d07f64f5 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -2,14 +2,14 @@ namespace Baikal\Core; -use Error; +use Baikal\Model\Principal\LDAP as Principal; use Exception; ################################################################# # Copyright notice # -# (c) 2022 Aisha Tammy -# (c) 2022 El-Virus +# (c) 2022 Aisha Tammy +# (c) 2022-2025 El-Virus # All rights reserved # # http://sabre.io/baikal @@ -83,7 +83,7 @@ class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic { * * @return string */ - protected function patternReplace($line, $username) { + public static function patternReplace($line, $username) { $user_split = [$username]; $user = $username; $domain = ''; @@ -93,12 +93,11 @@ protected function patternReplace($line, $username) { if (2 == count($user_split)) { $domain = $user_split[1]; } - } catch (Exception $ignored) { - } + } catch (\Exception $ignored) {} $domain_split = []; try { $domain_split = array_reverse(explode('.', $domain)); - } catch (Exception $ignored) { + } catch (\Exception $ignored) { $domain_split = []; } @@ -140,7 +139,7 @@ protected function patternReplace($line, $username) { * * @return bool */ - protected function doesBind(&$conn, $dn, $password) { + public static function doesBind(&$conn, $dn, $password) { try { $bind = ldap_bind($conn, $dn, $password); if ($bind) { @@ -176,7 +175,13 @@ public function __construct(\PDO $pdo, $table_name, $ldap_config) { * @return bool */ protected function ldapOpen($username, $password) { - $conn = \ldap_connect($this->ldap_config->ldap_uri); + try { + $principal = new Principal($username, $this->ldap_config); + } catch (Exception $ignored) { + return false; + } + + $conn = ldap_connect($this->ldap_config->ldap_uri); if (!$conn) { return false; } @@ -184,108 +189,23 @@ protected function ldapOpen($username, $password) { return false; } - $success = false; - - if ($this->ldap_config->ldap_mode == 'DN') { - $dn = $this->patternReplace($this->ldap_config->ldap_dn, $username); - - $success = $this->doesBind($conn, $dn, $password); - } elseif ($this->ldap_config->ldap_mode == 'Attribute' || $this->ldap_config->ldap_mode == 'Group') { - try { - if (!$this->doesBind($conn, $this->ldap_config->ldap_bind_dn, $this->ldap_config->ldap_bind_password)) { - return false; - } - - $attribute = $this->ldap_config->ldap_search_attribute; - $attribute = $this->patternReplace($attribute, $username); - - $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_config->ldap_search_base, '(' . $attribute . ')', - [explode('=', $attribute, 2)[0]], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; - - if ((!isset($result)) || (!isset($result["dn"]))) { - return false; - } - - $dn = $result["dn"]; - - if ($this->ldap_config->ldap_mode == 'Group') { - $inGroup = false; - $members = ldap_get_entries($conn, ldap_read($conn, $this->ldap_config->ldap_group, '(objectClass=*)', - ['member', 'uniqueMember'], 0, 0, 0, LDAP_DEREF_NEVER, []))[0]; - if (isset($members["member"])) { - foreach ($members["member"] as $member) { - if ($member == $result["dn"]) { - $inGroup = true; - break; - } - } - } - if (isset($members["uniqueMember"])) { - foreach ($members["uniqueMember"] as $member) { - if ($member == $result["dn"]) { - $inGroup = false; - break; - } - } - } - if (!$inGroup) { - return false; - } - } - - $success = $this->doesBind($conn, $dn, $password); - } catch (\ErrorException $e) { - error_log($e->getMessage()); - error_log(ldap_error($conn)); - } - } elseif ($this->ldap_config->ldap_mode == 'Filter') { - try { - if (!$this->doesBind($conn, $this->ldap_config->ldap_bind_dn, $this->ldap_config->ldap_bind_password)) { - return false; - } - - $filter = $this->ldap_config->ldap_search_filter; - $filter = $this->patternReplace($filter, $username); + $success = $this->doesBind($conn, $principal->dn, $password); - $result = ldap_get_entries($conn, ldap_search($conn, $this->ldap_config->ldap_search_base, $filter, [], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; - - $dn = $result["dn"]; - $success = $this->doesBind($conn, $dn, $password); - } catch (\ErrorException $e) { - error_log($e->getMessage()); - error_log(ldap_error($conn)); - } - } else { - error_log('Unknown LDAP authentication mode'); - } + ldap_close($conn); if ($success) { - $stmt = $this->pdo->prepare('SELECT username, digesta1 FROM ' . $this->table_name . ' WHERE username = ?'); + $stmt = $this->pdo->prepare('SELECT 1 FROM ' . $this->table_name . ' WHERE username = ?'); $stmt->execute([$username]); $result = $stmt->fetchAll(); if (empty($result)) { - $search_results = ldap_read($conn, $dn, '(objectclass=*)', [$this->ldap_config->ldap_cn, $this->ldap_config->ldap_mail]); - $entry = ldap_get_entries($conn, $search_results); - $user_displayname = $username; - $user_email = 'unset-email'; - if (!empty($entry[0][$this->ldap_config->ldap_cn])) { - $user_displayname = $entry[0][$this->ldap_config->ldap_cn][0]; - } - if (!empty($entry[0][$this->ldap_config->ldap_mail])) { - $user_email = $entry[0][$this->ldap_config->ldap_mail][0]; - } - $user = new \Baikal\Model\User(); + $user->set('federation', 'LDAP'); $user->set('username', $username); - $user->set('displayname', $user_displayname); - $user->set('email', $user_email); $user->persist(); } } - ldap_close($conn); - return $success; } diff --git a/Core/Frameworks/Baikal/Core/Server.php b/Core/Frameworks/Baikal/Core/Server.php index 3d479268f..208f2fdc4 100644 --- a/Core/Frameworks/Baikal/Core/Server.php +++ b/Core/Frameworks/Baikal/Core/Server.php @@ -27,6 +27,7 @@ namespace Baikal\Core; +use Baikal\Model\Structs\LDAPConfig; use Symfony\Component\Yaml\Yaml; /** @@ -135,19 +136,7 @@ protected function initServer() { } elseif ($this->authType === 'Apache') { $authBackend = new \Sabre\DAV\Auth\Backend\Apache(); } elseif ($this->authType === 'LDAP') { - $LDAPConfig = new \Baikal\Model\Structs\LDAPConfig(); - $LDAPConfig->ldap_mode = $config['system']['ldap_mode']; - $LDAPConfig->ldap_uri = $config['system']['ldap_uri']; - $LDAPConfig->ldap_bind_dn = $config['system']['ldap_bind_dn']; - $LDAPConfig->ldap_bind_password = $config['system']['ldap_bind_password']; - $LDAPConfig->ldap_dn = $config['system']['ldap_dn']; - $LDAPConfig->ldap_cn = $config['system']['ldap_cn']; - $LDAPConfig->ldap_mail = $config['system']['ldap_mail']; - $LDAPConfig->ldap_search_base = $config['system']['ldap_search_base']; - $LDAPConfig->ldap_search_attribute = $config['system']['ldap_search_attribute']; - $LDAPConfig->ldap_search_filter = $config['system']['ldap_search_filter']; - $LDAPConfig->ldap_group = $config['system']['ldap_group']; - $authBackend = new \Baikal\Core\LDAP($this->pdo, 'users', $LDAPConfig); + $authBackend = new \Baikal\Core\LDAP($this->pdo, 'users', LDAPConfig::fromArray($config['system'])); } else { $authBackend = new \Sabre\DAV\Auth\Backend\PDO($this->pdo); $authBackend->setRealm($this->authRealm); diff --git a/Core/Frameworks/Baikal/Model/Principal.php b/Core/Frameworks/Baikal/Model/Principal/DBPrincipal.php similarity index 91% rename from Core/Frameworks/Baikal/Model/Principal.php rename to Core/Frameworks/Baikal/Model/Principal/DBPrincipal.php index 2b86b1ae4..1cdb84090 100644 --- a/Core/Frameworks/Baikal/Model/Principal.php +++ b/Core/Frameworks/Baikal/Model/Principal/DBPrincipal.php @@ -25,11 +25,12 @@ # This copyright notice MUST APPEAR in all copies of the script! ################################################################# -namespace Baikal\Model; +namespace Baikal\Model\Principal; -class Principal extends \Flake\Core\Model\Db { +class DBPrincipal extends \Flake\Core\Model\Db { const DATATABLE = "principals"; const PRIMARYKEY = "id"; + public const EDITABLE = true; protected $aData = [ "uri" => "", "displayname" => "", diff --git a/Core/Frameworks/Baikal/Model/Principal/LDAP.php b/Core/Frameworks/Baikal/Model/Principal/LDAP.php new file mode 100644 index 000000000..509289a73 --- /dev/null +++ b/Core/Frameworks/Baikal/Model/Principal/LDAP.php @@ -0,0 +1,181 @@ + +# All rights reserved +# +# http://sabre.io/baikal +# +# This script is part of the Baïkal Server project. The Baïkal +# Server project is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# This copyright notice MUST APPEAR in all copies of the script! +################################################################# + +namespace Baikal\Model\Principal; + +use \Exception; +use Symfony\Component\Yaml\Yaml; +use \Baikal\Core\LDAP as LDAPCore; +use Baikal\Model\Structs\LDAPConfig; + +/** @phpstan-consistent-constructor */ +class LDAP extends DBPrincipal { + public const EDITABLE = false; + public readonly string $dn; + + protected $aData = [ + "uri" => "", + "displayname" => "", + "email" => "", + ]; + + public function __construct($username, $ldap_config = NULL) { + if (!isset($ldap_config)) { + $config = Yaml::parseFile(PROJECT_PATH_CONFIG . 'baikal.yaml'); + $ldap_config = LDAPConfig::fromArray($config['system']); + unset($config); + } + + $conn = ldap_connect($ldap_config->ldap_uri); + if (!$conn) { + throw new Exception('LDAP connect failed'); + } + if (!ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3)) { + throw new Exception('LDAP server does not support protocol version 3'); + } + + try { + switch ($ldap_config->ldap_mode) { + case 'DN': + $this->dn = LDAPCore::patternReplace($ldap_config->ldap_dn, $username); + break; + + case 'Attribute': + case 'Group': + try { + if (!LDAPCore::doesBind($conn, $ldap_config->ldap_bind_dn, $ldap_config->ldap_bind_password)) { + throw new Exception('LDAP Service user fails to bind'); + } + + $attribute = $ldap_config->ldap_search_attribute; + $attribute = LDAPCore::patternReplace($attribute, $username); + + $result = ldap_get_entries($conn, ldap_search($conn, $ldap_config->ldap_search_base, '(' . $attribute . ')', + [$ldap_config->ldap_search_attribute], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; + + if ((!isset($result)) || (!isset($result['dn']))) { + throw new Exception('No LDAP entry matches Attribute'); + } + + if ($ldap_config->ldap_mode == 'Group') { + $inGroup = false; + $members = ldap_get_entries($conn, ldap_read($conn, $ldap_config->ldap_group, '(objectClass=*)', + ['member', 'uniqueMember'], 0, 0, 0, LDAP_DEREF_NEVER, []))[0]; + if (isset($members['member'])) { + foreach ($members['member'] as $member) { + if ($member == $result['dn']) { + $inGroup = true; + break; + } + } + } + if (isset($members['uniqueMember'])) { + foreach ($members['uniqueMember'] as $member) { + if ($member == $result['dn']) { + $inGroup = false; + break; + } + } + } + if (!$inGroup) { + throw new Exception('The user is not in the specified Group'); + } + } + + $this->dn = $result['dn']; + } catch (\ErrorException $e) { + error_log($e->getMessage()); + error_log(ldap_error($conn)); + throw new Exception('LDAP error'); + } + break; + + case 'Filter': + try { + if (!LDAPCore::doesBind($conn, $ldap_config->ldap_bind_dn, $ldap_config->ldap_bind_password)) { + throw new Exception('LDAP Service user fails to bind'); + } + + $filter = $this->ldap_config->ldap_search_filter; + $filter = LDAPCore::patternReplace($filter, $username); + + $result = ldap_get_entries($conn, ldap_search($conn, $ldap_config->ldap_search_base, $filter, [], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; + + $this->dn = $result['dn']; + } catch (\ErrorException $e) { + error_log($e->getMessage()); + error_log(ldap_error($conn)); + throw new Exception('LDAP error'); + } + break; + + default: + error_log('Unknown LDAP authentication mode'); + throw new Exception('Unknown LDAP authentication mode'); + } + + $results = ldap_read($conn, $this->dn, '(objectclass=*)', [$ldap_config->ldap_cn, $ldap_config->ldap_mail]); + $entry = ldap_get_entries($conn, $results)[0]; + $displayname = $username; + $email = 'unset-email'; + if (!empty($entry[$ldap_config->ldap_cn])) { + $displayname = $entry[$ldap_config->ldap_cn][0]; + } + if (!empty($entry[$ldap_config->ldap_mail])) { + $email = $entry[$ldap_config->ldap_mail][0]; + } + + parent::set('uri', 'principals/' . $username); + parent::set('displayname', $displayname); + parent::set('email', $email); + } finally { + ldap_close($conn); + } + } + + static function fromPrincipal($db_principal, $username) { + $principal = new static($username); + + if (isset($db_principal)) { + $principal->aData[$db_principal->getPrimaryKey()] = $db_principal->getPrimary(); + $principal->bFloating = false; + } + + $principal->persist(); + + return $principal; + } + + function set($sPropName, $sPropValue) { + if (!array_key_exists($sPropName, $this->aData)) { + parent::set($sPropName, $sPropValue); + } + } + function persist() { return parent::persist(); } + function destroy() { return parent::destroy(); } +}; + +?> \ No newline at end of file diff --git a/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php b/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php index c50995391..b44eb1601 100644 --- a/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php +++ b/Core/Frameworks/Baikal/Model/Structs/LDAPConfig.php @@ -3,7 +3,7 @@ ################################################################# # Copyright notice # -# (c) 2022 El-Virus +# (c) 2022-2025 El-Virus # All rights reserved # # http://sabre.io/baikal @@ -30,7 +30,7 @@ /** * Struct that holds the Configuration parameters for LDAP authentication. */ -class LDAPConfig { +final class LDAPConfig { public $ldap_mode; public $ldap_uri; public $ldap_bind_dn; @@ -42,4 +42,22 @@ class LDAPConfig { public $ldap_search_attribute; public $ldap_search_filter; public $ldap_group; + + public static function fromArray($array) { + $LDAPConfig = new static(); + + $LDAPConfig->ldap_mode = $array['ldap_mode']; + $LDAPConfig->ldap_uri = $array['ldap_uri']; + $LDAPConfig->ldap_bind_dn = $array['ldap_bind_dn']; + $LDAPConfig->ldap_bind_password = $array['ldap_bind_password']; + $LDAPConfig->ldap_dn = $array['ldap_dn']; + $LDAPConfig->ldap_cn = $array['ldap_cn']; + $LDAPConfig->ldap_mail = $array['ldap_mail']; + $LDAPConfig->ldap_search_base = $array['ldap_search_base']; + $LDAPConfig->ldap_search_attribute = $array['ldap_search_attribute']; + $LDAPConfig->ldap_search_filter = $array['ldap_search_filter']; + $LDAPConfig->ldap_group = $array['ldap_group']; + + return $LDAPConfig; + } } diff --git a/Core/Frameworks/Baikal/Model/User.php b/Core/Frameworks/Baikal/Model/User.php index 4bb9ade97..83cdc78be 100644 --- a/Core/Frameworks/Baikal/Model/User.php +++ b/Core/Frameworks/Baikal/Model/User.php @@ -27,6 +27,7 @@ namespace Baikal\Model; +use Exception; use Symfony\Component\Yaml\Yaml; class User extends \Flake\Core\Model\Db { @@ -35,6 +36,7 @@ class User extends \Flake\Core\Model\Db { const LABELFIELD = "username"; protected $aData = [ + "federation" => NULL, "username" => "", "digesta1" => "", ]; @@ -45,10 +47,23 @@ function initByPrimary($sPrimary) { parent::initByPrimary($sPrimary); # Initializing principals - $this->oIdentityPrincipal = \Baikal\Model\Principal::getBaseRequester() + $dbPrincipal = \Baikal\Model\Principal\DBPrincipal::getBaseRequester() ->addClauseEquals("uri", "principals/" . $this->get("username")) ->execute() ->first(); + + switch (parent::get("federation")) { + case NULL: + $this->oIdentityPrincipal = $dbPrincipal; + break; + + case "LDAP": + $this->oIdentityPrincipal = \Baikal\Model\Principal\LDAP::fromPrincipal($dbPrincipal, $this->get("username")); + break; + + default: + throw new Exception("Unknown user federation"); + } } function getAddressBooksBaseRequester() { @@ -75,7 +90,7 @@ function initFloating() { parent::initFloating(); # Initializing principals - $this->oIdentityPrincipal = new \Baikal\Model\Principal(); + $this->oIdentityPrincipal = new \Baikal\Model\Principal\DBPrincipal(); } function get($sPropName) { @@ -287,4 +302,8 @@ function getPasswordHashForPassword($sPassword) { return md5($this->get("username") . ':' . $config['system']['auth_realm'] . ':' . $sPassword); } + + function isEditable() { + return $this->oIdentityPrincipal::class::EDITABLE; + } } diff --git a/Core/Frameworks/BaikalAdmin/Controller/Users.php b/Core/Frameworks/BaikalAdmin/Controller/Users.php index 6ef90c039..db3bf7b67 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Users.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Users.php @@ -71,6 +71,7 @@ function render() { "username" => $user->get("username"), "displayname" => $user->get("displayname"), "email" => $user->get("email"), + "federation" => $user->get("federation"), ]; } @@ -120,10 +121,12 @@ protected function actionEditRequested() { protected function actionEdit() { $aParams = $this->getParams(); $this->oModel = new \Baikal\Model\User(intval($aParams["edit"])); - $this->initForm(); + if ($this->oModel->isEditable()) { + $this->initForm(); - if ($this->oForm->submitted()) { - $this->oForm->execute(); + if ($this->oForm->submitted()) { + $this->oForm->execute(); + } } } @@ -217,6 +220,9 @@ function linkNew() { } static function linkEdit(\Baikal\Model\User $user) { + if (!$user->isEditable()) + return NULL; + return self::buildRoute([ "edit" => $user->get("id"), ]) . "#form"; diff --git a/Core/Frameworks/BaikalAdmin/Resources/Templates/Users.html b/Core/Frameworks/BaikalAdmin/Resources/Templates/Users.html index 042234739..af81b0671 100644 --- a/Core/Frameworks/BaikalAdmin/Resources/Templates/Users.html +++ b/Core/Frameworks/BaikalAdmin/Resources/Templates/Users.html @@ -10,6 +10,9 @@

Users

{{ user.username|escape }}
+ {% if user.federation %} + {{ user.federation }} + {% endif %} {{ user.displayname|escape }} <{{ user.email|escape }}> @@ -17,7 +20,9 @@

Users

Calendars Address Books + {% if user.linkedit %} Edit + {% endif %} Delete

diff --git a/Core/Resources/Db/MySQL/db.sql b/Core/Resources/Db/MySQL/db.sql index 51757cda0..0342101d4 100644 --- a/Core/Resources/Db/MySQL/db.sql +++ b/Core/Resources/Db/MySQL/db.sql @@ -140,6 +140,7 @@ CREATE TABLE propertystorage ( CREATE UNIQUE INDEX path_property ON propertystorage (path(600), name(100)); CREATE TABLE users ( id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + federation VARCHAR(20), username VARBINARY(50), digesta1 VARBINARY(32), UNIQUE(username) diff --git a/Core/Resources/Db/SQLite/db.sql b/Core/Resources/Db/SQLite/db.sql index 6d3bf7c73..22d91c944 100644 --- a/Core/Resources/Db/SQLite/db.sql +++ b/Core/Resources/Db/SQLite/db.sql @@ -141,6 +141,7 @@ CREATE TABLE propertystorage ( CREATE UNIQUE INDEX path_property ON propertystorage (path, name); CREATE TABLE users ( id integer primary key asc NOT NULL, + federation TEXT, username TEXT NOT NULL, digesta1 TEXT NOT NULL, UNIQUE(username) From 176d2b8031e9b55e2ce55e5672635263326c52e2 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Sun, 19 Jan 2025 19:14:16 +0100 Subject: [PATCH 39/41] Fix user federation code style --- Core/Frameworks/Baikal/Core/LDAP.php | 6 +- .../Baikal/Model/Principal/LDAP.php | 57 ++++++++++--------- Core/Frameworks/Baikal/Model/User.php | 7 +-- .../BaikalAdmin/Controller/Users.php | 3 +- 4 files changed, 39 insertions(+), 34 deletions(-) diff --git a/Core/Frameworks/Baikal/Core/LDAP.php b/Core/Frameworks/Baikal/Core/LDAP.php index 4d07f64f5..81a11dd43 100644 --- a/Core/Frameworks/Baikal/Core/LDAP.php +++ b/Core/Frameworks/Baikal/Core/LDAP.php @@ -3,7 +3,6 @@ namespace Baikal\Core; use Baikal\Model\Principal\LDAP as Principal; -use Exception; ################################################################# # Copyright notice @@ -93,7 +92,8 @@ public static function patternReplace($line, $username) { if (2 == count($user_split)) { $domain = $user_split[1]; } - } catch (\Exception $ignored) {} + } catch (\Exception $ignored) { + } $domain_split = []; try { $domain_split = array_reverse(explode('.', $domain)); @@ -177,7 +177,7 @@ public function __construct(\PDO $pdo, $table_name, $ldap_config) { protected function ldapOpen($username, $password) { try { $principal = new Principal($username, $this->ldap_config); - } catch (Exception $ignored) { + } catch (\Exception $ignored) { return false; } diff --git a/Core/Frameworks/Baikal/Model/Principal/LDAP.php b/Core/Frameworks/Baikal/Model/Principal/LDAP.php index 509289a73..cbcbb352a 100644 --- a/Core/Frameworks/Baikal/Model/Principal/LDAP.php +++ b/Core/Frameworks/Baikal/Model/Principal/LDAP.php @@ -26,10 +26,9 @@ namespace Baikal\Model\Principal; -use \Exception; -use Symfony\Component\Yaml\Yaml; -use \Baikal\Core\LDAP as LDAPCore; +use Baikal\Core\LDAP as LDAPCore; use Baikal\Model\Structs\LDAPConfig; +use Symfony\Component\Yaml\Yaml; /** @phpstan-consistent-constructor */ class LDAP extends DBPrincipal { @@ -42,7 +41,7 @@ class LDAP extends DBPrincipal { "email" => "", ]; - public function __construct($username, $ldap_config = NULL) { + public function __construct($username, $ldap_config = null) { if (!isset($ldap_config)) { $config = Yaml::parseFile(PROJECT_PATH_CONFIG . 'baikal.yaml'); $ldap_config = LDAPConfig::fromArray($config['system']); @@ -51,10 +50,10 @@ public function __construct($username, $ldap_config = NULL) { $conn = ldap_connect($ldap_config->ldap_uri); if (!$conn) { - throw new Exception('LDAP connect failed'); + throw new \Exception('LDAP connect failed'); } if (!ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3)) { - throw new Exception('LDAP server does not support protocol version 3'); + throw new \Exception('LDAP server does not support protocol version 3'); } try { @@ -67,19 +66,19 @@ public function __construct($username, $ldap_config = NULL) { case 'Group': try { if (!LDAPCore::doesBind($conn, $ldap_config->ldap_bind_dn, $ldap_config->ldap_bind_password)) { - throw new Exception('LDAP Service user fails to bind'); + throw new \Exception('LDAP Service user fails to bind'); } - + $attribute = $ldap_config->ldap_search_attribute; $attribute = LDAPCore::patternReplace($attribute, $username); - + $result = ldap_get_entries($conn, ldap_search($conn, $ldap_config->ldap_search_base, '(' . $attribute . ')', [$ldap_config->ldap_search_attribute], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; - + if ((!isset($result)) || (!isset($result['dn']))) { - throw new Exception('No LDAP entry matches Attribute'); + throw new \Exception('No LDAP entry matches Attribute'); } - + if ($ldap_config->ldap_mode == 'Group') { $inGroup = false; $members = ldap_get_entries($conn, ldap_read($conn, $ldap_config->ldap_group, '(objectClass=*)', @@ -101,40 +100,40 @@ public function __construct($username, $ldap_config = NULL) { } } if (!$inGroup) { - throw new Exception('The user is not in the specified Group'); + throw new \Exception('The user is not in the specified Group'); } } - + $this->dn = $result['dn']; } catch (\ErrorException $e) { error_log($e->getMessage()); error_log(ldap_error($conn)); - throw new Exception('LDAP error'); + throw new \Exception('LDAP error'); } break; case 'Filter': try { if (!LDAPCore::doesBind($conn, $ldap_config->ldap_bind_dn, $ldap_config->ldap_bind_password)) { - throw new Exception('LDAP Service user fails to bind'); + throw new \Exception('LDAP Service user fails to bind'); } - + $filter = $this->ldap_config->ldap_search_filter; $filter = LDAPCore::patternReplace($filter, $username); - + $result = ldap_get_entries($conn, ldap_search($conn, $ldap_config->ldap_search_base, $filter, [], 0, 1, 0, LDAP_DEREF_ALWAYS, []))[0]; - + $this->dn = $result['dn']; } catch (\ErrorException $e) { error_log($e->getMessage()); error_log(ldap_error($conn)); - throw new Exception('LDAP error'); + throw new \Exception('LDAP error'); } break; - + default: error_log('Unknown LDAP authentication mode'); - throw new Exception('Unknown LDAP authentication mode'); + throw new \Exception('Unknown LDAP authentication mode'); } $results = ldap_read($conn, $this->dn, '(objectclass=*)', [$ldap_config->ldap_cn, $ldap_config->ldap_mail]); @@ -168,14 +167,20 @@ static function fromPrincipal($db_principal, $username) { return $principal; } - + function set($sPropName, $sPropValue) { if (!array_key_exists($sPropName, $this->aData)) { parent::set($sPropName, $sPropValue); - } + } } - function persist() { return parent::persist(); } - function destroy() { return parent::destroy(); } + + function persist() { + return parent::persist(); + } + + function destroy() { + return parent::destroy(); + } }; ?> \ No newline at end of file diff --git a/Core/Frameworks/Baikal/Model/User.php b/Core/Frameworks/Baikal/Model/User.php index 83cdc78be..8f946d859 100644 --- a/Core/Frameworks/Baikal/Model/User.php +++ b/Core/Frameworks/Baikal/Model/User.php @@ -27,7 +27,6 @@ namespace Baikal\Model; -use Exception; use Symfony\Component\Yaml\Yaml; class User extends \Flake\Core\Model\Db { @@ -36,7 +35,7 @@ class User extends \Flake\Core\Model\Db { const LABELFIELD = "username"; protected $aData = [ - "federation" => NULL, + "federation" => null, "username" => "", "digesta1" => "", ]; @@ -53,7 +52,7 @@ function initByPrimary($sPrimary) { ->first(); switch (parent::get("federation")) { - case NULL: + case null: $this->oIdentityPrincipal = $dbPrincipal; break; @@ -62,7 +61,7 @@ function initByPrimary($sPrimary) { break; default: - throw new Exception("Unknown user federation"); + throw new \Exception("Unknown user federation"); } } diff --git a/Core/Frameworks/BaikalAdmin/Controller/Users.php b/Core/Frameworks/BaikalAdmin/Controller/Users.php index db3bf7b67..25dd6316b 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Users.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Users.php @@ -220,8 +220,9 @@ function linkNew() { } static function linkEdit(\Baikal\Model\User $user) { - if (!$user->isEditable()) + if (!$user->isEditable()) { return NULL; + } return self::buildRoute([ "edit" => $user->get("id"), From 2c855b22a4d6f703128f4a7b6b6d74a1aa503dad Mon Sep 17 00:00:00 2001 From: El-Virus Date: Sun, 19 Jan 2025 19:16:27 +0100 Subject: [PATCH 40/41] Fix user federation code style v2 --- Core/Frameworks/Baikal/Model/Principal/LDAP.php | 10 +++++----- Core/Frameworks/BaikalAdmin/Controller/Users.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/Frameworks/Baikal/Model/Principal/LDAP.php b/Core/Frameworks/Baikal/Model/Principal/LDAP.php index cbcbb352a..b597a9653 100644 --- a/Core/Frameworks/Baikal/Model/Principal/LDAP.php +++ b/Core/Frameworks/Baikal/Model/Principal/LDAP.php @@ -1,4 +1,5 @@ \ No newline at end of file diff --git a/Core/Frameworks/BaikalAdmin/Controller/Users.php b/Core/Frameworks/BaikalAdmin/Controller/Users.php index 25dd6316b..6e638d7f6 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Users.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Users.php @@ -221,7 +221,7 @@ function linkNew() { static function linkEdit(\Baikal\Model\User $user) { if (!$user->isEditable()) { - return NULL; + return null; } return self::buildRoute([ From 4f2b4925e634e418106b5d6ac35221cb8dde1d03 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Sun, 19 Jan 2025 19:17:32 +0100 Subject: [PATCH 41/41] Fix user federation code style v3 --- Core/Frameworks/Baikal/Model/Principal/LDAP.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/Frameworks/Baikal/Model/Principal/LDAP.php b/Core/Frameworks/Baikal/Model/Principal/LDAP.php index b597a9653..694792aa8 100644 --- a/Core/Frameworks/Baikal/Model/Principal/LDAP.php +++ b/Core/Frameworks/Baikal/Model/Principal/LDAP.php @@ -182,5 +182,4 @@ function persist() { function destroy() { return parent::destroy(); } -}; - +}