2017-11-18 11:33:03 +01:00
< ? php
/*
* This program 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 3 of the License , or
* ( at your option ) any later version .
*
* This program 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 .
*
* You should have received a copy of the GNU General Public License
2021-02-09 00:29:04 +01:00
* along with this program . If not , see < https :// www . gnu . org / licenses />.
2017-11-18 11:33:03 +01:00
*/
/**
* libreNMS HTTP - Authentication and LDAP Authorization Library
2021-09-10 20:09:53 +02:00
*
2017-11-18 11:33:03 +01:00
* @ author Maximilian Wilhelm < max @ rfc2324 . org >
* @ copyright 2016 LibreNMS , Barbarossa
* @ license GPL
*/
namespace LibreNMS\Authentication ;
2018-09-12 12:51:24 -05:00
use App\Models\User ;
2017-11-18 11:33:03 +01:00
use LibreNMS\Config ;
2023-08-28 00:13:40 -05:00
use LibreNMS\Enum\LegacyAuthLevel ;
2017-11-18 11:33:03 +01:00
use LibreNMS\Exceptions\AuthenticationException ;
2019-02-21 12:08:35 -06:00
use LibreNMS\Exceptions\LdapMissingException ;
2017-11-18 11:33:03 +01:00
class LdapAuthorizationAuthorizer extends AuthorizerBase
{
2018-09-18 07:57:23 -05:00
use LdapSessionCache ;
2017-11-18 11:33:03 +01:00
protected $ldap_connection ;
2021-04-01 17:35:18 +02:00
protected static $AUTH_IS_EXTERNAL = true ;
2017-11-18 11:33:03 +01:00
public function __construct ()
{
2017-11-28 09:19:34 -06:00
if ( ! function_exists ( 'ldap_connect' )) {
2019-02-21 12:08:35 -06:00
throw new LdapMissingException ();
2017-11-28 09:19:34 -06:00
}
2017-11-18 11:33:03 +01:00
/**
* Set up connection to LDAP server
*/
$this -> ldap_connection = @ ldap_connect ( Config :: get ( 'auth_ldap_server' ), Config :: get ( 'auth_ldap_port' ));
if ( ! $this -> ldap_connection ) {
2022-09-06 23:43:51 +02:00
throw new AuthenticationException ( 'Fatal error while connecting to LDAP server, uri not valid: ' . Config :: get ( 'auth_ldap_server' ) . ':' . Config :: get ( 'auth_ldap_port' ));
2017-11-18 11:33:03 +01:00
}
if ( Config :: get ( 'auth_ldap_version' )) {
ldap_set_option ( $this -> ldap_connection , LDAP_OPT_PROTOCOL_VERSION , Config :: get ( 'auth_ldap_version' ));
}
2022-05-20 00:17:44 +02:00
if ( Config :: get ( 'auth_ldap_starttls' ) && ( Config :: get ( 'auth_ldap_starttls' ) == 'optional' || Config :: get ( 'auth_ldap_starttls' ) == 'required' )) {
2017-11-18 11:33:03 +01:00
$tls = ldap_start_tls ( $this -> ldap_connection );
2022-05-20 00:17:44 +02:00
if ( Config :: get ( 'auth_ldap_starttls' ) == 'required' && $tls === false ) {
2017-11-28 09:19:34 -06:00
throw new AuthenticationException ( 'Fatal error: LDAP TLS required but not successfully negotiated:' . ldap_error ( $this -> ldap_connection ));
2017-11-18 11:33:03 +01:00
}
}
2022-04-22 08:28:29 +02:00
if (( Config :: has ( 'auth_ldap_binduser' ) || Config :: has ( 'auth_ldap_binddn' )) && Config :: has ( 'auth_ldap_bindpassword' )) {
if ( Config :: get ( 'auth_ldap_binddn' ) == null ) {
Config :: set ( 'auth_ldap_binddn' , $this -> getFullDn ( Config :: get ( 'auth_ldap_binduser' )));
}
$username = Config :: get ( 'auth_ldap_binddn' );
$password = Config :: get ( 'auth_ldap_bindpassword' );
$bind_result = ldap_bind ( $this -> ldap_connection , $username , $password );
if ( ! $bind_result ) {
throw new AuthenticationException ( 'Fatal error: LDAP bind configured but not successfully authenticated:' . ldap_error ( $this -> ldap_connection ));
}
}
2017-11-18 11:33:03 +01:00
}
2019-03-05 00:24:14 -06:00
public function authenticate ( $credentials )
2017-11-18 11:33:03 +01:00
{
2019-03-05 00:24:14 -06:00
if ( isset ( $credentials [ 'username' ]) && $this -> userExists ( $credentials [ 'username' ])) {
2018-09-12 12:51:24 -05:00
return true ;
}
2017-11-18 11:33:03 +01:00
2018-09-12 12:51:24 -05:00
$guest = Config :: get ( 'http_auth_guest' );
if ( $guest && User :: thisAuth () -> where ( 'username' , $guest ) -> exists ()) {
2017-11-18 11:33:03 +01:00
return true ;
}
throw new AuthenticationException ();
}
public function userExists ( $username , $throw_exception = false )
{
if ( $this -> authLdapSessionCacheGet ( 'user_exists' )) {
2021-04-01 17:35:18 +02:00
return true ;
2017-11-18 11:33:03 +01:00
}
$filter = '(' . Config :: get ( 'auth_ldap_prefix' ) . $username . ')' ;
$search = ldap_search ( $this -> ldap_connection , trim ( Config :: get ( 'auth_ldap_suffix' ), ',' ), $filter );
2024-06-24 19:49:34 -05:00
if ( $search === false ) {
throw new AuthenticationException ( 'User search failed: ' . ldap_error ( $this -> ldap_connection ));
}
2017-11-18 11:33:03 +01:00
$entries = ldap_get_entries ( $this -> ldap_connection , $search );
if ( $entries [ 'count' ]) {
/*
* Cache positiv result as this will result in more queries which we
* want to speed up .
*/
$this -> authLdapSessionCacheSet ( 'user_exists' , 1 );
2020-09-21 14:54:51 +02:00
2021-04-01 17:35:18 +02:00
return true ;
2017-11-18 11:33:03 +01:00
}
/*
* Don 't cache that user doesn' t exists as this might be a misconfiguration
* on some end and the user will be happy if it " just works " after the user
* has been added to LDAP .
*/
2021-04-01 17:35:18 +02:00
return false ;
2017-11-18 11:33:03 +01:00
}
2023-08-28 23:38:09 -05:00
public function getRoles ( string $username ) : array | false
2017-11-18 11:33:03 +01:00
{
2023-08-28 00:13:40 -05:00
$roles = $this -> authLdapSessionCacheGet ( 'roles' );
if ( $roles !== null ) {
return $roles ;
2017-11-18 11:33:03 +01:00
}
2023-08-28 00:13:40 -05:00
$roles = [];
2017-11-18 11:33:03 +01:00
// Find all defined groups $username is in
2023-05-24 22:21:54 +02:00
$filter = '(&(|(cn=' . implode ( ')(cn=' , array_keys ( Config :: get ( 'auth_ldap_groups' ))) . '))(' . Config :: get ( 'auth_ldap_groupmemberattr' ) . '=' . $this -> getMembername ( $username ) . '))' ;
2017-11-18 11:33:03 +01:00
$search = ldap_search ( $this -> ldap_connection , Config :: get ( 'auth_ldap_groupbase' ), $filter );
2024-06-24 19:49:34 -05:00
if ( $search === false ) {
throw new AuthenticationException ( 'Role search failed: ' . ldap_error ( $this -> ldap_connection ));
}
2017-11-18 11:33:03 +01:00
$entries = ldap_get_entries ( $this -> ldap_connection , $search );
2023-08-28 00:13:40 -05:00
$authLdapGroups = Config :: get ( 'auth_ldap_groups' );
// Collect all roles
2017-11-18 11:33:03 +01:00
foreach ( $entries as $entry ) {
2023-08-30 12:33:13 -05:00
if ( isset ( $entry [ 'cn' ][ 0 ])) {
$groupname = $entry [ 'cn' ][ 0 ];
if ( isset ( $authLdapGroups [ $groupname ][ 'roles' ]) && is_array ( $authLdapGroups [ $groupname ][ 'roles' ])) {
$roles = array_merge ( $roles , $authLdapGroups [ $groupname ][ 'roles' ]);
} elseif ( isset ( $authLdapGroups [ $groupname ][ 'level' ])) {
$role = LegacyAuthLevel :: tryFrom ( $authLdapGroups [ $groupname ][ 'level' ]) ? -> getName ();
if ( $role ) {
$roles [] = $role ;
}
2023-08-28 00:13:40 -05:00
}
2017-11-18 11:33:03 +01:00
}
}
2023-08-28 00:13:40 -05:00
$roles = array_unique ( $roles );
$this -> authLdapSessionCacheSet ( 'roles' , $roles );
2020-09-21 14:54:51 +02:00
2023-08-28 00:13:40 -05:00
return $roles ;
2017-11-18 11:33:03 +01:00
}
public function getUserid ( $username )
{
$user_id = $this -> authLdapSessionCacheGet ( 'userid' );
if ( isset ( $user_id )) {
return $user_id ;
}
2018-09-12 12:51:24 -05:00
$guest_username = Config :: get ( 'http_auth_guest' );
$user_id = User :: thisAuth () -> where ( 'username' , $guest_username ) -> value ( 'auth_id' ) ? : - 1 ;
2017-11-18 11:33:03 +01:00
$filter = '(' . Config :: get ( 'auth_ldap_prefix' ) . $username . ')' ;
$search = ldap_search ( $this -> ldap_connection , trim ( Config :: get ( 'auth_ldap_suffix' ), ',' ), $filter );
$entries = ldap_get_entries ( $this -> ldap_connection , $search );
if ( $entries [ 'count' ]) {
2023-08-09 14:30:16 +12:00
$user_id = $entries [ 0 ][ 'uidnumber' ][ 0 ];
2018-09-12 12:51:24 -05:00
}
if ( $user_id === - 1 ) {
// no user or guest user, don't allow
if ( $guest_username ) {
throw new AuthenticationException ();
} else {
throw new AuthenticationException ( 'Guest login allowed.' );
}
2017-11-18 11:33:03 +01:00
}
$this -> authLdapSessionCacheSet ( 'userid' , $user_id );
2020-09-21 14:54:51 +02:00
2017-11-18 11:33:03 +01:00
return $user_id ;
}
2023-08-28 00:13:40 -05:00
public function getUser ( $user_id )
2017-11-18 11:33:03 +01:00
{
2023-08-28 00:13:40 -05:00
$uid_attr = strtolower ( Config :: get ( 'auth_ldap_uid_attribute' , 'uidnumber' ));
$filter = " ( $uid_attr = $user_id ) " ;
2017-11-18 11:33:03 +01:00
$search = ldap_search ( $this -> ldap_connection , trim ( Config :: get ( 'auth_ldap_suffix' ), ',' ), $filter );
$entries = ldap_get_entries ( $this -> ldap_connection , $search );
if ( $entries [ 'count' ]) {
2023-08-28 00:13:40 -05:00
$entry = $entries [ 0 ];
$username = $entry [ 'uid' ][ 0 ];
$realname = $entry [ 'cn' ][ 0 ];
$user_id = $entry [ 'uidnumber' ][ 0 ];
$email = $entry [ Config :: get ( 'auth_ldap_emailattr' )][ 0 ];
$ldap_groups = $this -> getGroupList ();
foreach ( $ldap_groups as $ldap_group ) {
$ldap_comparison = ldap_compare (
$this -> ldap_connection ,
$ldap_group ,
Config :: get ( 'auth_ldap_groupmemberattr' ),
$this -> getMembername ( $username )
);
if ( ! Config :: has ( 'auth_ldap_group' ) || $ldap_comparison === true ) {
return [
'username' => $username ,
'realname' => $realname ,
2024-01-05 05:39:12 +01:00
'user_id' => $user_id ,
'email' => $email ,
2023-08-28 00:13:40 -05:00
];
2017-11-18 11:33:03 +01:00
}
}
}
2021-04-01 17:35:18 +02:00
return false ;
2017-11-18 11:33:03 +01:00
}
2022-04-22 08:28:29 +02:00
/**
* Get the full dn with auth_ldap_prefix and auth_ldap_suffix
*
* @ internal
*
* @ return string
*/
protected function getFullDn ( string $username )
{
return Config :: get ( 'auth_ldap_prefix' , '' ) . $username . Config :: get ( 'auth_ldap_suffix' , '' );
}
2017-11-18 11:33:03 +01:00
protected function getMembername ( $username )
{
if ( Config :: get ( 'auth_ldap_groupmembertype' ) == 'fulldn' ) {
$membername = Config :: get ( 'auth_ldap_prefix' ) . $username . Config :: get ( 'auth_ldap_suffix' );
} elseif ( Config :: get ( 'auth_ldap_groupmembertype' ) == 'puredn' ) {
$filter = '(' . Config :: get ( 'auth_ldap_attr.uid' ) . '=' . $username . ')' ;
$search = ldap_search ( $this -> ldap_connection , Config :: get ( 'auth_ldap_groupbase' ), $filter );
$entries = ldap_get_entries ( $this -> ldap_connection , $search );
$membername = $entries [ 0 ][ 'dn' ];
} else {
$membername = $username ;
}
return $membername ;
}
public function getGroupList ()
{
$ldap_groups = [];
$default_group = 'cn=groupname,ou=groups,dc=example,dc=com' ;
if ( Config :: has ( 'auth_ldap_group' )) {
if ( Config :: get ( 'auth_ldap_group' ) !== $default_group ) {
$ldap_groups [] = Config :: get ( 'auth_ldap_group' );
}
}
foreach ( Config :: get ( 'auth_ldap_groups' ) as $key => $value ) {
$dn = " cn= $key , " . Config :: get ( 'auth_ldap_groupbase' );
$ldap_groups [] = $dn ;
}
return $ldap_groups ;
}
}