Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for LDAP. #1124

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9ef2ecc
add SMTP email sending, LDAP authentication with auto user creation
epsilon-0 Jul 4, 2022
74e92aa
Added support for LDAP filters and attribute search
El-Virus Jul 19, 2022
6cc928e
Removed new field hiding system and changed LDAP and SMTP parameters …
El-Virus Jul 19, 2022
32aeff2
Add support for LDAP Groups, minor fixes
El-Virus Jul 20, 2022
16922d2
Added Morphology hook to Standard Settings Controller of Baikal Admin.
El-Virus Jul 24, 2022
75dbdf1
Merge commit '9ef2ecc184c72332f142061452f261e117d60986'
El-Virus Jul 24, 2022
7b2bb3e
Merge commit '74e92aa3c48bc3cdce3d16e3b3fc552cd8cc9318'
El-Virus Jul 24, 2022
e8237c9
Merge commit '6cc928e050a77789622a8d450ac60ebba76fe254'
El-Virus Jul 24, 2022
7abdc21
Merge commit '32aeff23ef9bd05d07a7d22a775501c9e36ad47b'
El-Virus Jul 24, 2022
11fd40d
Added missing refresh on "WebDAV authentication type" change
El-Virus Jul 24, 2022
248a4a8
Fix LDAP.php, according to linter.
El-Virus Jul 24, 2022
9806378
Fix Standard.php, according to linter.
El-Virus Jul 24, 2022
4eb5981
Fix (BaikalAdmin) Standard.php, according to linter.
El-Virus Jul 24, 2022
996ea8d
Added Curly Braces to if statements.
El-Virus Jul 28, 2022
7d6067f
Added a couple of missing spaces
El-Virus Jul 28, 2022
42a926b
Added quotation marks surrounding url, and a period.
El-Virus Jul 30, 2022
25741a6
Fixed https://github.com/sabre-io/Baikal/pull/1124#issuecomment-12394…
El-Virus Sep 7, 2022
d9c9d3d
Fixed linter errors
El-Virus Sep 7, 2022
7fda407
fix the patternReplace function
epsilon-0 Sep 7, 2022
34bc4a9
LDAP bind Password hidden
El-Virus Sep 8, 2022
e8b2178
Merge pull request #1 from bsd-ac/LDAP
El-Virus Sep 8, 2022
3e6ab43
Fix LDAP.php, according to linter.
El-Virus Sep 8, 2022
38ca1f0
Epsilon0's merge fix
El-Virus Sep 8, 2022
f8d25b4
Merge remote-tracking branch 'refs/remotes/upstream/master'
El-Virus Oct 9, 2022
3d3e756
Actually allow LDAP bind password to be set
El-Virus Oct 15, 2022
6e455eb
Added LDAP Config Struct and default LDAP Params to dist config.
El-Virus Oct 16, 2022
9a175df
Fix LDAP.php, according to linter.
El-Virus Oct 16, 2022
0bfa4a8
Moved Structs folder to correct location.
El-Virus Oct 16, 2022
8633f78
Fix LDAPConfig.php, according to linter.
El-Virus Oct 16, 2022
b7d68b3
Added empty value on config set safeguard.
El-Virus Oct 17, 2022
bf0288f
Fix LDAP.php's license
El-Virus Oct 30, 2022
4fb8397
Fix LDAPConfig.php's license
El-Virus Oct 30, 2022
171dab0
Changed copyright notice. Added check for empty bind password.
El-Virus Nov 4, 2022
8885a9f
Changed $username to $dn
El-Virus Nov 20, 2022
3f4c6a7
Removed an article from a settings label
El-Virus Dec 31, 2022
8583553
Removed an article from a settings label
El-Virus Dec 31, 2022
afb5d38
Added slash to ldap_connect
El-Virus Dec 31, 2022
de8e4ff
Fixed typo in settings
El-Virus Dec 31, 2022
12b4121
Remove articles from config page.
El-Virus Dec 31, 2022
b492d70
Fixed 'Undefined array key 0' on incorrect username
El-Virus Jan 13, 2023
cd967f1
Added check for LDAP extension availability
El-Virus Jan 13, 2023
4b3213a
Fix LDAP.php, according to linter
El-Virus Jan 13, 2023
3677285
Merge commit 'aa7e340113545f8be18b6e8c44d001fc4e684526'
El-Virus Jun 23, 2023
d62f271
Applied standard settings morphology hook to initialization wizard.
El-Virus Jun 23, 2023
d576221
Merge remote-tracking branch 'refs/remotes/upstream/master'
El-Virus Aug 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
309 changes: 309 additions & 0 deletions Core/Frameworks/Baikal/Core/LDAP.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
<?php

namespace Baikal\Core;

use Error;
use Exception;

#################################################################
# Copyright notice
#
# (c) 2022 Aisha Tammy <[email protected]>
# (c) 2022 El-Virus <[email protected]>
# 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.
*/
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 Config.
* LDAP Config Struct.
*
* @var \Baikal\Model\Structs\LDAPConfig
*/
protected $ldap_config;

/**
* Replaces patterns for their assigned value using the
* given username, using cyrus-sasl style replacements.
*
* %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 [email protected]:
* %u = [email protected]
* %U = jane.doe
* %d = mail.example.org
* %1 = org
* %2 = example
* %3 = mail
*
* @param string $line
* @param string $username
*
* @return string
*/
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 = [];
}

$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;
}

/**
* 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.
*
* @param \PD0 $pdo
* @param string $table_name
* @param \Baikal\Model\Structs\LDAPConfig $ldap_config
*/
public function __construct(\PDO $pdo, $table_name, $ldap_config) {
$this->pdo = $pdo;
$this->table_name = $table_name;
$this->ldap_config = $ldap_config;
}

/**
* 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_config->ldap_uri);
if (!$conn) {
return false;
}
if (!ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3)) {
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);

$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');
}

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=*)', [$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('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) {
if (!extension_loaded("ldap")) {
error_log('PHP LDAP extension not enabled');

return false;
}

return $this->ldapOpen($username, $password);
}
}
14 changes: 14 additions & 0 deletions Core/Frameworks/Baikal/Core/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,20 @@ 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') {
$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);
Expand Down
Loading
Loading