diff --git a/.gitignore b/.gitignore index ef624d1..afd34bf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ config_ldap.php config_db.php data certs - +.vscode diff --git a/Demo/docker-compose.yaml b/Demo/docker-compose.yaml index ae44233..9425cd5 100644 --- a/Demo/docker-compose.yaml +++ b/Demo/docker-compose.yaml @@ -45,6 +45,9 @@ services: ldap_filter: "(objectClass=*)" ldap_bind_dn: "cn=butler,dc=example,dc=com" ldap_bind_pass: "readonly" + ldap_secure: 0 + ldap_cert_path: "/var/www/html/oauth/certs/cert.crt" + ldap_key_path: "/var/www/html/oauth/certs/private.key" db_host: "database" db_port: "5432" db_type: "pgsql" diff --git a/Docker/php-ldap-pgsql/Dockerfile b/Docker/php-ldap-pgsql/Dockerfile index f901b35..073add2 100644 --- a/Docker/php-ldap-pgsql/Dockerfile +++ b/Docker/php-ldap-pgsql/Dockerfile @@ -1,13 +1,17 @@ -FROM php:fpm +FROM php:8.3.4-fpm-alpine RUN set -x \ - && apt-get update \ - && apt-get install -y libpq-dev libldap2-dev git\ - && rm -rf /var/lib/apt/lists/* \ + && apk update \ + && apk add --no-cache libpq-dev git \ && docker-php-ext-configure pgsql --with-pgsql=/usr/local/pgsql \ - && docker-php-ext-install pdo pdo_pgsql pgsql \ - && docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ \ - && docker-php-ext-install ldap + && docker-php-ext-install pdo pdo_pgsql pgsql + +RUN set -x \ + && apk add --no-cache --virtual .build-dependencies-in-virtual-world openldap-dev \ + && apk add --no-cache libldap \ + && docker-php-ext-install ldap \ + && docker-php-ext-enable ldap \ + && apk del .build-dependencies-in-virtual-world # Enable development php.ini config (Solve empty answer from token.php) RUN ln -s /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini diff --git a/README.md b/README.md index 0f7e14e..2e2142b 100755 --- a/README.md +++ b/README.md @@ -160,6 +160,9 @@ Edit `oauth/LDAP/config_ldap.php` and adapt prameters with your LDAP configurati | ldap_base_dn | The base directory name of your LDAP server | `ou=People,o=Company` | | ldap_bind_dn | The LDAP Directory Name of an service account to allow LDAP search | | | ldap_bind_pass | The password associated to the service account to allow LDAP search | | +| ldap_secure | LDAP over ldaps (ex:ldaps://ldap.google.com) | `false` | +| ldap_cert_path | LDAP certificate file for secure LDAP | `/var/www/html/oauth/certs/certificate.crt`| +| ldap_key_path | LDAP private key file for secure LDAP | `/var/www/html/oauth/certs/key.key`| For openLDAP server, the 'ldap_search_attribute' should be `uid`, and for AD server this must be `sAMAccountName`. Nevertheless, 'email' or 'cn' could be used, this depends on your LDAP configuration. diff --git a/oauth/LDAP/LDAP.php b/oauth/LDAP/LDAP.php index 3d13cfe..406a8ae 100755 --- a/oauth/LDAP/LDAP.php +++ b/oauth/LDAP/LDAP.php @@ -14,20 +14,26 @@ class LDAP implements LDAPInterface protected $ldap_server; /** - * LDAP Resource - * - * @param string @ldap_host - * Either a hostname or, with OpenLDAP 2.x.x and later, a full LDAP URI - * @param int @ldap_port - * An optional int to specify ldap server port, by default : 389 - * @param int @ldap_version - * An optional int to specify ldap version, by default LDAP V3 protocol is used - * @param boolean @ldap_start_tls - * An optional boolean to use ldap over STARTTLS, by default LDAP STARTTLS is not used - * - * Initiate LDAP connection by creating an associated resource - */ - public function __construct($ldap_host, $ldap_port = 389, $ldap_version = 3, $ldap_start_tls = false) + * LDAP Resource + * + * @param string @ldap_host + * Either a hostname or, with OpenLDAP 2.x.x and later, a full LDAP URI + * @param int @ldap_port + * An optional int to specify ldap server port, by default : 389 + * @param int @ldap_version + * An optional int to specify ldap version, by default LDAP V3 protocol is used + * @param boolean @ldap_start_tls + * An optional boolean to use ldap over STARTTLS, by default LDAP STARTTLS is not used + * @param string @ldap_cert_path + * An optional string to specify the path to the certificate file + * @param string @ldap_key_path + * An optional string to specify the path to the key file + * @param boolean @ldap_secure + * An optional boolean to use ldap over secure connection, by default LDAP secure connection is not used + * + * Initiate LDAP connection by creating an associated resource + */ + public function __construct($ldap_host, $ldap_port = 389, $ldap_version = 3, $ldap_start_tls = false, $ldap_cert_path, $ldap_key_path, $ldap_secure = false) { if (!is_string($ldap_host)) { throw new InvalidArgumentException('First argument to LDAP must be the hostname of a ldap server (string). Ex: ldap//example.com/ '); @@ -37,12 +43,23 @@ public function __construct($ldap_host, $ldap_port = 389, $ldap_version = 3, $ld throw new InvalidArgumentException('Second argument to LDAP must be the ldap server port (int). Ex : 389'); } + // Connect to LDAP server using secure connection with certificate and key + if ($ldap_secure === true) { + if (!is_string($ldap_cert_path)) { + throw new InvalidArgumentException('Fifth argument to LDAP must be the path to the certificate file (string).'); + } + if (!is_string($ldap_key_path)) { + throw new InvalidArgumentException('Sixth argument to LDAP must be the path to the key file (string).'); + } + } + $ldap = ldap_connect($ldap_host, $ldap_port) - or die("Unable to connect to the ldap server : $ldaphost ! Please check your configuration."); + or die("Unable to connect to the ldap server : $ldap_host ! Please check your configuration."); // Support LDAP V3 since many users have encountered difficulties with LDAP V3. if (is_int($ldap_version) && $ldap_version <= 3 && $ldap_version > 0) { ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, $ldap_version); + ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); } else { throw new InvalidArgumentException('Third argument to LDAP must be the ldap version (int). Ex : 3'); } diff --git a/oauth/LDAP/config_ldap.php.example b/oauth/LDAP/config_ldap.php.example index 4a1c4c1..89ce028 100755 --- a/oauth/LDAP/config_ldap.php.example +++ b/oauth/LDAP/config_ldap.php.example @@ -4,6 +4,11 @@ $ldap_host = getenv('ldap_host') ?: "ldap://ldap.company.com/"; $ldap_port = intval(getenv('ldap_port')) ?: 389; $ldap_version = intval(getenv('ldap_version')) ?: 3; $ldap_start_tls = boolval(getenv('ldap_start_tls')) ?: false; +$ldap_secure = boolval(getenv('ldap_secure')) ?: false; + +// Certificates +$ldap_cert_path = getenv('ldap_cert_path') ?: "/var/www/html/oauth/certs/certificate.crt"; +$ldap_key_path = getenv('ldap_key_path') ?: "/var/www/html/oauth/certs/key.key"; // Attribute use to identify user on LDAP - ex : uid, mail, sAMAccountName $ldap_search_attribute = getenv('ldap_search_attribute') ?: "uid"; @@ -15,3 +20,4 @@ $ldap_filter = getenv('ldap_filter') ?: "(objectClass=*)"; // ldap service user to allow search in ldap $ldap_bind_dn = getenv('ldap_bind_dn') ?: ""; $ldap_bind_pass = getenv('ldap_bind_pass') ?: ""; + diff --git a/oauth/index.php b/oauth/index.php index 9a7e804..a14accd 100644 --- a/oauth/index.php +++ b/oauth/index.php @@ -7,14 +7,15 @@ // include our LDAP object -require_once __DIR__.'/LDAP/LDAP.php'; -require_once __DIR__.'/LDAP/config_ldap.php'; +require_once __DIR__ . '/LDAP/LDAP.php'; +require_once __DIR__ . '/LDAP/config_ldap.php'; $prompt_template = new DOMDocument(); $prompt_template->loadHTMLFile('form_prompt.html'); -function messageShow($html_template, $message = 'No Msg') { +function messageShow($html_template, $message = 'No Msg') +{ $modification_node = $html_template->getElementsByTagName('div')->item(5); $page_fragment = $html_template->createDocumentFragment(); $page_fragment->appendXML($message); @@ -24,66 +25,55 @@ function messageShow($html_template, $message = 'No Msg') { echo $html_template->saveHTML(); } - -// Verify all fields have been filled -if (empty($_POST['user']) || empty($_POST['password'])) -{ - if (empty($_POST['user'])) { - messageShow($prompt_template, 'Username field can\'t be empty.'); +if ($_SERVER['REQUEST_METHOD'] === 'GET') { + // If user is already authenticated, redirect him to the authorize page + messageShow($prompt_template, ""); +} elseif ($_SERVER['REQUEST_METHOD'] !== 'POST') { + // Verify all fields have been filled + if (empty($_POST['user']) || empty($_POST['password'])) { + if (empty($_POST['user'])) { + messageShow($prompt_template, 'Username field can\'t be empty.'); + } else { + messageShow($prompt_template, 'Password field can\'t be empty.'); + } } else { - messageShow($prompt_template, 'Password field can\'t be empty.'); - } -} -else -{ - // Check received data length (to prevent code injection) - if (strlen($_POST['user']) > 64) - { - messageShow($prompt_template, 'Username has incorrect format ... Please try again'); - } - elseif (strlen($_POST['password']) > 64) - { - messageShow($prompt_template, 'Password has incorrect format ... Please try again'); - } - else - { - // Remove every html tag and useless space on username (to prevent XSS) - $user=strtolower(strip_tags(htmlspecialchars(trim($_POST['user'])))); - $password=$_POST['password']; + // Check received data length (to prevent code injection) + if (strlen($_POST['user']) > 64) { + messageShow($prompt_template, 'Username has incorrect format ... Please try again'); + } elseif (strlen($_POST['password']) > 64) { + messageShow($prompt_template, 'Password has incorrect format ... Please try again'); + } else { + // Remove every html tag and useless space on username (to prevent XSS) + $user = strtolower(strip_tags(htmlspecialchars(trim($_POST['user'])))); + $password = $_POST['password']; - // Open a LDAP connection - $ldap = new LDAP($ldap_host,$ldap_port,$ldap_version,$ldap_start_tls); + // Open a LDAP connection + $ldap = new LDAP($ldap_host, $ldap_port, $ldap_version, $ldap_start_tls, $ldap_cert_path, $ldap_key_path, $ldap_secure); - // Check user credential on LDAP - try{ - $authenticated = $ldap->checkLogin($user,$password,$ldap_search_attribute,$ldap_filter,$ldap_base_dn,$ldap_bind_dn,$ldap_bind_pass); - } - catch (Exception $e) - { - $authenticated = false; - } + // Check user credential on LDAP + try { + $authenticated = $ldap->checkLogin($user, $password, $ldap_search_attribute, $ldap_filter, $ldap_base_dn, $ldap_bind_dn, $ldap_bind_pass); + } catch (Exception $e) { + $authenticated = false; + } - // If user is authenticated - if ($authenticated) - { - $_SESSION['uid']=$user; + // If user is authenticated + if ($authenticated) { + $_SESSION['uid'] = $user; - // If user came here with an autorize request, redirect him to the authorize page. Else prompt a simple message. - if (isset($_SESSION['auth_page'])) - { - $auth_page=$_SESSION['auth_page']; - header('Location: ' . $auth_page); - exit(); + // If user came here with an autorize request, redirect him to the authorize page. Else prompt a simple message. + if (isset($_SESSION['auth_page'])) { + $auth_page = $_SESSION['auth_page']; + header('Location: ' . $auth_page); + exit(); + } else { + messageShow($prompt_template, 'Congratulation you are authenticated !

However there is nothing to do here ...'); + } } - else - { - messageShow($prompt_template, 'Congratulation you are authenticated !

However there is nothing to do here ...'); + // check login on LDAP has failed. Login and password were invalid or LDAP is unreachable + else { + messageShow($prompt_template, 'Authentication failed ... Check your username and password.
If the error persists contact your administrator.

'); } } - // check login on LDAP has failed. Login and password were invalid or LDAP is unreachable - else - { - messageShow($prompt_template, 'Authentication failed ... Check your username and password.
If the error persists contact your administrator.

'); - } } } diff --git a/oauth/resource.php b/oauth/resource.php index f2739b4..c25e34c 100755 --- a/oauth/resource.php +++ b/oauth/resource.php @@ -27,7 +27,7 @@ $assoc_id = intval($info_oauth["assoc_id"]); // Open a LDAP connection -$ldap = new LDAP($ldap_host, $ldap_port, $ldap_version, $ldap_start_tls); +$ldap = new LDAP($ldap_host, $ldap_port, $ldap_version, $ldap_start_tls, $ldap_cert_path, $ldap_key_path, $ldap_secure); // Try to get user data on the LDAP try {