2016-08-10 13:52:55 +00:00
< ? php
/**
* Implementation of user authentication
*
2021-09-20 14:42:14 +00:00
* @ category DMS
* @ package SeedDMS
* @ author Uwe Steinmann < uwe @ steinmann . cx >
* @ license GPL 2
* @ version @ version @
* @ copyright 2010 - 2016 Uwe Steinmann
* @ version Release : @ package_version @
2016-08-10 13:52:55 +00:00
*/
require_once " inc.ClassAuthentication.php " ;
/**
* Abstract class to authenticate user against ldap server
*
2021-09-20 14:42:14 +00:00
* @ category DMS
* @ package SeedDMS
* @ author Uwe Steinmann < uwe @ steinmann . cx >
* @ copyright 2010 - 2016 Uwe Steinmann
* @ version Release : @ package_version @
2016-08-10 13:52:55 +00:00
*/
class SeedDMS_LdapAuthentication extends SeedDMS_Authentication {
2022-11-28 20:36:40 +00:00
var $dms ;
2023-08-16 16:45:20 +00:00
var $settings ;
protected function addUser ( $username , $info ) {
2024-05-14 13:33:45 +00:00
$mailfield = ! empty ( $settings -> _ldapMailField ) ? $settings -> _ldapMailField : 'mail' ;
return $this -> dms -> addUser ( $username , null , $info [ 'cn' ][ 0 ], isset ( $info [ $mailfield ]) ? $info [ $mailfield ][ 0 ] : '' , $this -> settings -> _language , $this -> settings -> _theme , " User was added from LDAP " );
2023-08-16 16:45:20 +00:00
}
protected function updateUser ( $user , $info ) {
2024-05-14 13:33:45 +00:00
$mailfield = ! empty ( $settings -> _ldapMailField ) ? $settings -> _ldapMailField : 'mail' ;
2023-08-16 16:45:20 +00:00
if ( isset ( $info [ 'cn' ][ 0 ]) && ( $info [ 'cn' ][ 0 ] != $user -> getFullName ())) {
$user -> setFullName ( $info [ 'cn' ][ 0 ]);
}
2024-05-14 13:33:45 +00:00
if ( isset ( $info [ $mailfield ][ 0 ]) && ( $info [ $mailfield ][ 0 ] != $user -> getEmail ())) {
$user -> setEmail ( $info [ $mailfield ][ 0 ]);
2023-08-16 16:45:20 +00:00
}
}
2022-11-28 20:36:40 +00:00
2023-08-17 11:51:02 +00:00
protected function syncGroups ( $user , $ldapgroups ) {
$groupnames = [];
$count = 0 ;
if ( isset ( $ldapgroups [ 'count' ]))
$count = ( int ) $ldapgroups [ 'count' ];
for ( $i = 0 ; $i < $count ; $i ++ ) {
2024-02-17 12:03:29 +00:00
if ( 0 ) {
/* ldap_explode_dn () turns all utf - 8 chars into \xx
* This needs to be undone with the following regex .
*/
$tmp = ldap_explode_dn ( $ldapgroups [ $i ], 1 );
$tmp [ 0 ] = preg_replace_callback ( '/\\\([0-9A-Fa-f]{2})/' , function ( $matches ) { return chr ( hexdec ( $matches [ 1 ])); }, $tmp [ 0 ]);
} else {
/* Second option would be to not using ldap_explode_dn ()
* and just extract the cn with
* preg_match ( '/[^cn=]([^,]*)/i' , $ldapgroups [ $i ], $tmp );
*/
preg_match ( '/[^cn=]([^,]*)/i' , $ldapgroups [ $i ], $tmp );
}
2023-08-17 11:51:02 +00:00
if ( ! in_array ( $tmp [ 0 ], $groupnames )) {
2024-02-17 12:03:29 +00:00
$groupnames [] = preg_replace_callback ( '/\\\([0-9A-Fa-f]{2})/' , function ( $matches ) { return chr ( hexdec ( $matches [ 1 ])); }, $tmp [ 0 ]);
2023-08-17 11:51:02 +00:00
}
}
/* Remove user from all groups not listed in LDAP */
$usergroups = $user -> getGroups ();
foreach ( $usergroups as $usergroup ) {
if ( ! in_array ( $usergroup -> getName (), $groupnames ))
$user -> leaveGroup ( $usergroup );
}
/* Add new groups and make user a member of it */
if ( $groupnames ) {
foreach ( $groupnames as $groupname ) {
$group = $this -> dms -> getGroupByName ( $groupname );
if ( $group ) { /* Group already exists, just join it */
$user -> joinGroup ( $group );
} else { /* Add group and join it */
$newgroup = $this -> dms -> addGroup ( $groupname , 'Added during LDAP Authentication' );
if ( $newgroup ) {
$user -> joinGroup ( $newgroup );
}
}
}
}
}
2022-11-28 20:36:40 +00:00
public function __construct ( $dms , $settings ) { /* {{{ */
$this -> dms = $dms ;
$this -> settings = $settings ;
} /* }}} */
2016-08-10 13:52:55 +00:00
/**
* Do ldap authentication
*
* This method supports active directory and open ldap servers . Others may work but
* are not tested .
* The authentication is done in two steps .
* 1. First an anonymous bind is done and the user who wants to login is searched
* for . If it is found the cn of that user will be used for the bind in step 2.
* If the user cannot be found the second step will use a cn : cn =< username > , < basedn >
* 2. A second bind with a password and cn will be executed . This is the actuall
* authentication . If that succeeds the user is logged in . If the user doesn ' t
* exist in the database , it will be created .
*
2021-09-20 14:42:14 +00:00
* @ param string $username name of user to authenticate
* @ param string $password password of user to authenticate
2016-08-10 13:52:55 +00:00
* @ return object | boolean user object if authentication was successful otherwise false
*/
public function authenticate ( $username , $password ) { /* {{{ */
$settings = $this -> settings ;
$dms = $this -> dms ;
if ( isset ( $settings -> _ldapPort ) && is_int ( $settings -> _ldapPort )) {
$ds = ldap_connect ( $settings -> _ldapHost , $settings -> _ldapPort );
} else {
$ds = ldap_connect ( $settings -> _ldapHost );
}
if ( ! is_bool ( $ds )) {
/* Check if ldap base dn is set, and use ldap server if it is */
2023-08-25 10:33:35 +00:00
/* $tmpDN will be set to a 'wild' guess how the user ' s dn might
* look like if searching for that user didn ' t return a dn .
*/
2016-08-10 13:52:55 +00:00
if ( isset ( $settings -> _ldapBaseDN )) {
2024-03-20 17:21:22 +00:00
$ldapSearchAttribut = " uid " ;
2024-03-20 13:02:24 +00:00
/* $tmpDN will only be used as a last resort if searching for the user failed */
2022-08-30 16:11:00 +00:00
$tmpDN = " uid= " . $username . " , " . $settings -> _ldapBaseDN ;
2016-08-10 13:52:55 +00:00
}
/* Active directory has a different base dn */
if ( isset ( $settings -> _ldapType )) {
if ( $settings -> _ldapType == 1 ) {
2024-03-20 17:21:22 +00:00
$ldapSearchAttribut = " sAMAccountName " ;
2024-03-20 13:02:24 +00:00
/* $tmpDN will only be used as a last resort if searching for the user failed */
2016-08-10 13:52:55 +00:00
$tmpDN = $username . '@' . $settings -> _ldapAccountDomainName ;
// Add the following if authentication with an Active Dir doesn't work
// See https://sourceforge.net/p/seeddms/discussion/general/thread/19c70d8d/
// and http://stackoverflow.com/questions/6222641/how-to-php-ldap-search-to-get-user-ou-if-i-dont-know-the-ou-for-base-dn
ldap_set_option ( $ds , LDAP_OPT_REFERRALS , 0 );
}
}
// Ensure that the LDAP connection is set to use version 3 protocol.
// Required for most authentication methods, including SASL.
ldap_set_option ( $ds , LDAP_OPT_PROTOCOL_VERSION , 3 );
// try an authenticated/anonymous bind first.
// If it succeeds, get the DN for the user and use it for an authentication
// with the users password.
$bind = false ;
2023-08-24 11:13:11 +00:00
if ( ! empty ( $settings -> _ldapBindDN )) {
2016-08-10 13:52:55 +00:00
$bind = @ ldap_bind ( $ds , $settings -> _ldapBindDN , $settings -> _ldapBindPw );
} else {
$bind = @ ldap_bind ( $ds );
}
2024-03-20 17:21:22 +00:00
2016-08-10 13:52:55 +00:00
$dn = false ;
2024-03-20 17:21:22 +00:00
/* The simplest search is just the username */
$ldapsearchterm = $ldapSearchAttribut . '=' . $username ;
/* If login by email is allowed , the search for user name is ored with
* the search for the email .
*/
if ( $settings -> _enableLoginByEmail ) {
$ldapsearchterm = " |( " . $ldapsearchterm . " )(mail= " . $username . " ) " ;
}
/* If a ldap filter is set, it will be anded */
if ( $settings -> _ldapFilter ) {
$ldapsearchterm = " &( " . $ldapsearchterm . " ) " . $settings -> _ldapFilter ;
}
2023-08-24 11:13:11 +00:00
/* If bind succeed , then get the dn of the user . If a filter
* is set , it will be used to allow only those users to log in
* matching the filter criteria . Depending on the type of server ,
* ( AD or regular LDAP ), the search attribute is already set to
* 'sAMAccountName=' or 'uid=' . All other filters are ANDed .
* A common filter is '(mail=*)' to ensure a user has an email
* address .
2024-03-20 17:21:22 +00:00
* If the previous bind failed , we could try later to bind with
* the user ' s credentials ( this was until 6.0 . 26 and 5.1 . 33 the case ),
* but if login by email is allowed , it makes no sense to try it . The
* only way to bind is by using a correct dn and that cannot be
* formed with an email .
2023-08-24 11:13:11 +00:00
*/
2016-08-10 13:52:55 +00:00
if ( $bind ) {
2024-03-20 17:21:22 +00:00
/*
2023-08-25 18:17:47 +00:00
if ( ! empty ( $settings -> _ldapFilter )) {
2024-03-20 17:21:22 +00:00
$search = ldap_search ( $ds , $settings -> _ldapBaseDN , " (&( " . $ldapSearchAttribut . '=' . $username . " ) " . $settings -> _ldapFilter . " ) " );
2016-08-10 13:52:55 +00:00
} else {
2024-03-20 17:21:22 +00:00
$search = ldap_search ( $ds , $settings -> _ldapBaseDN , $ldapSearchAttribut . '=' . $username );
2016-08-10 13:52:55 +00:00
}
2024-03-20 17:21:22 +00:00
*/
$search = ldap_search ( $ds , $settings -> _ldapBaseDN , " ( " . $ldapsearchterm . " ) " );
2016-08-10 13:52:55 +00:00
if ( ! is_bool ( $search )) {
$info = ldap_get_entries ( $ds , $search );
if ( ! is_bool ( $info ) && $info [ " count " ] > 0 ) {
$dn = $info [ 0 ][ 'dn' ];
2024-03-20 17:21:22 +00:00
/* Set username to login name in case the email was used for authentication */
2024-05-14 09:57:05 +00:00
$username = $info [ 0 ][ strtolower ( $ldapSearchAttribut )][ 0 ];
2016-08-10 13:52:55 +00:00
}
}
2024-03-20 17:21:22 +00:00
} elseif ( ! empty ( $settings -> _enableLoginByEmail )) {
ldap_close ( $ds );
return null ;
2016-08-10 13:52:55 +00:00
}
/* If the previous bind failed , try it with the users creditionals
2023-08-25 10:33:35 +00:00
* by simply setting $dn to a guessed dn ( see above )
2023-08-25 18:17:47 +00:00
* Don ' t do this if a filter is set because users filtered out
2023-08-25 10:33:35 +00:00
* may still be able to authenticate , because $tmpDN could be a
2023-08-25 18:17:47 +00:00
* valid DN which do not match the filter criteria .
* Example : if baseDN is 'dc=seeddms,dc=org' and the
2023-08-25 10:33:35 +00:00
* user 'test' logs in , then $tmpDN will be 'uid=test,dc=seeddms,dc=org'
* If that user was filtered out , because filter was set to '(mail=*)'
* and the user doesn ' t have a mail address , then $dn will not be
* set and $tmpDN will be used instead , allowing a successfull bind .
2024-03-20 17:21:22 +00:00
* Also do not take the $tmpDN if login by email is allowed , because
* the username could be the email and that doesn ' t form a valid dn .
2016-08-10 13:52:55 +00:00
*/
2024-03-20 17:21:22 +00:00
if ( is_bool ( $dn ) && empty ( $settings -> _ldapFilter ) && empty ( $settings -> _enableLoginByEmail )) {
2016-08-10 13:52:55 +00:00
$dn = $tmpDN ;
}
2023-08-25 18:17:47 +00:00
/* Without a dn don't even try to bind. It won't work anyway */
if ( ! $dn ) {
ldap_close ( $ds );
return null ;
}
2023-08-16 15:40:14 +00:00
/* Check if user already exists in the database . Return with an error
* only if the sql statements fails , but not if no user was found .
2024-03-20 17:21:22 +00:00
* The username may not be the one passed to this function anymore . It
* could have been overwritten by uid ( or sAMAccountName ) derived from
* the above ldap search .
2023-08-16 15:40:14 +00:00
*/
2016-08-10 13:52:55 +00:00
$user = $dms -> getUserByLogin ( $username );
2018-01-24 08:18:24 +00:00
if ( $user === false ) {
ldap_close ( $ds );
return false ;
}
2016-08-10 13:52:55 +00:00
2023-08-16 15:40:14 +00:00
/* Now do the actual authentication of the user */
$bind = @ ldap_bind ( $ds , $dn , $password );
if ( ! $bind ) {
ldap_close ( $ds );
2023-08-25 18:17:47 +00:00
return null ;
2023-08-16 15:40:14 +00:00
}
2022-08-30 16:11:00 +00:00
2023-08-16 15:40:14 +00:00
// Successfully authenticated. Now check to see if the user exists within
// the database. If not, add them in if _restricted is not set,
2024-03-20 17:21:22 +00:00
// but do not set the password of the user.
2023-08-16 15:40:14 +00:00
if ( ! $settings -> _restricted ) {
2024-03-20 17:21:22 +00:00
/* Retrieve the user ' s LDAP information . At this time the username is
* the uid or sAMAccountName , even if the email was used for login .
*/
2023-08-16 15:40:14 +00:00
if ( isset ( $settings -> _ldapFilter ) && strlen ( $settings -> _ldapFilter ) > 0 ) {
2024-03-20 17:21:22 +00:00
$search = ldap_search ( $ds , $settings -> _ldapBaseDN , " (&( " . $ldapSearchAttribut . '=' . $username . " ) " . $settings -> _ldapFilter . " ) " );
2023-08-16 15:40:14 +00:00
} else {
2024-03-20 17:21:22 +00:00
$search = ldap_search ( $ds , $settings -> _ldapBaseDN , $ldapSearchAttribut . '=' . $username );
2023-08-16 15:40:14 +00:00
}
if ( ! is_bool ( $search )) {
$info = ldap_get_entries ( $ds , $search );
if ( ! is_bool ( $info ) && $info [ " count " ] == 1 && $info [ 0 ][ " count " ] > 0 ) {
if ( is_null ( $user )) {
2023-08-16 16:45:20 +00:00
$user = $this -> addUser ( $username , $info [ 0 ]);
2023-08-16 15:40:14 +00:00
} else {
2023-08-16 16:45:20 +00:00
$this -> updateUser ( $user , $info [ 0 ]);
2016-08-10 13:52:55 +00:00
}
2023-08-17 11:51:02 +00:00
/*
$this -> syncGroups ( $user , [
'count' => 4 ,
2023-08-18 06:01:21 +00:00
0 => 'CN=group1,OU=groups,DC=seeddms,DC=org' ,
1 => 'CN=group2,OU=groups,DC=seeddms,DC=org' ,
2 => 'CN=group3,OU=groups,DC=seeddms,DC=org' ,
3 => 'CN=group4,OU=groups,DC=seeddms,DC=org'
2023-08-17 11:51:02 +00:00
]
);
*/
if ( ! empty ( $settings -> _ldapGroupField ) && ! empty ( $info [ 0 ][ $settings -> _ldapGroupField ])) {
$this -> syncGroups ( $user , $info [ 0 ][ $settings -> _ldapGroupField ]);
}
2016-08-10 13:52:55 +00:00
}
}
}
ldap_close ( $ds );
return $user ;
} else {
return false ;
}
} /* }}} */
}