diff --git a/README.md b/README.md index d1e20be..5c2b338 100644 --- a/README.md +++ b/README.md @@ -28,27 +28,36 @@ Configuration * define( 'LDAPAUTH_BASE', 'dc=domain,dc=com' ); // Base DN (location of users) * define( 'LDAPAUTH_USERNAME_FIELD', 'uid'); // (optional) LDAP field name in which username is store -To use a privileged account for the user search: +### To use a privileged account for the user search: * define( 'LDAPAUTH_SEARCH_USER', 'cn=your-user,dc=domain,dc=com' ); // (optional) Privileged user to search with * define( 'LDAPAUTH_SEARCH_PASS', 'the-pass'); // (optional) (only if LDAPAUTH_SEARCH_USER set) Privileged user pass -To define a template to bind using the current user for the search: Use %s as the place holder for the current user name +### To define a template to bind using the current user for the search: Use %s as the place holder for the current user name * define( 'LDAPAUTH_BIND_WITH_USER_TEMPLATE', '%s@myad.domain' ); // (optional) Use %s as the place holder for the current user name -To check group membership before authenticating: +### To use a custom LDAP search filter: + * define( 'LDAPAUTH_SEARCH_FILTER', '(&(samaccountname=%s)(memberof=YOURLS-ADMINS,OU=Groups,DC=example,DC=com))' ); // Use %s as the place holder for the current user name + +If this option is not set, filter is based only on LDAPAUTH_USERNAME_FIELD (default). +This is useful if more advanced filter is needed. Like when using AD nested groups. +For searching based on AD Nested group `LDAP_MATCHING_RULE_IN_CHAIN` OID (1.2.840.113556.1.4.1941) must be specified for user's memberof attribute. +Example of filter based on AD nested group: +`define( 'LDAPAUTH_SEARCH_FILTER', '(&(samaccountname=%s)(memberof:1.2.840.113556.1.4.1941:=YOURLS-ADMINS,OU=Groups,DC=example,DC=com))' );` + +### To check group membership before authenticating: * define( 'LDAPAUTH_GROUP_ATTR', 'memberof' ); // (optional) LDAP groups attr * define( 'LDAPAUTH_GROUP_REQ', 'the-group;another-admin-group'); // (only if LDAPAUTH_GROUP_REQ set) Group/s user must be in. Allows multiple, semicolon delimited -To define the scope of group req search: +### To define the scope of group req search: * define( 'LDAPAUTH_GROUP_SCOP', 'sub' ); // if not defined the default is 'sub', and will check for the user in all the subtree. The other option is 'base', that will search only members of the exactly req -To define the type of user cache used: +### To define the type of user cache used: * define( 'LDAPAUTH_USERCACHE_TYPE', 0); // (optional) Defaults to 1, which caches users in the options table. 0 turns off cacheing. Other values are currently undefined, but may be one day -To automatically add LDAP users to config.php: +### To automatically add LDAP users to config.php: * define( 'LDAPAUTH_ADD_NEW', true ); // (optional) Add LDAP users to config.php -To use Active Directory Sites and Services DNS entry for LDAP server name lookup +### To use Active Directory Sites and Services DNS entry for LDAP server name lookup * define( 'LDAPAUTH_DNS_SITES_AND_SERVICES', '_ldap._tcp.corporate._sites.yourdomain.com' ); // If using Active Directory, with multiple Domain Controllers, the safe way to use DNS to look up your active LDAP server names. If set, it will be used to override the hostname portion of LDAPAUTH_HOST. * define( 'LDAPAUTH_HOST', 'ldap://'); // LDAP protocol without hostname. You can use 'ldaps://' for LDAP with TLS. diff --git a/plugin.php b/plugin.php index af62720..0848c10 100644 --- a/plugin.php +++ b/plugin.php @@ -44,7 +44,6 @@ function ldapauth_environment_check() { if ( !defined( 'LDAPAUTH_USERCACHE_TYPE' ) ) define( 'LDAPAUTH_USERCACHE_TYPE', 1 ); - global $ldapauth_authorized_admins; if ( !isset( $ldapauth_authorized_admins ) ) { @@ -61,58 +60,58 @@ function ldapauth_environment_check() { yourls_add_filter( 'is_valid_user', 'ldapauth_is_valid_user' ); function ldapauth_shuffle_assoc($list) { - if (!is_array($list)) return $list; + if (!is_array($list)) return $list; - $keys = array_keys($list); - shuffle($keys); - $random = array(); - foreach ($keys as $key) { - $random[$key] = $list[$key]; - } - return $random; + $keys = array_keys($list); + shuffle($keys); + $random = array(); + foreach ($keys as $key) { + $random[$key] = $list[$key]; + } + return $random; } // return list of Active Directory Ldap servers that are associated with a site and service // example for $site = = '_ldap._tcp.corporate._sites.company.com' function ldapauth_get_ad_servers_for_site() { - $results = []; - $ad_servers = dns_get_record(LDAPAUTH_DNS_SITES_AND_SERVICES, DNS_SRV, $authns, $addtl); - foreach ($ad_servers as $ad_server) { - array_push($results, $ad_server['target']); - } - $results = ldapauth_shuffle_assoc($results); #randomize the order - return $results; + $results = []; + $ad_servers = dns_get_record(LDAPAUTH_DNS_SITES_AND_SERVICES, DNS_SRV, $authns, $addtl); + foreach ($ad_servers as $ad_server) { + array_push($results, $ad_server['target']); + } + $results = ldapauth_shuffle_assoc($results); #randomize the order + return $results; } // returns ldap connection function ldapauth_get_ldap_connection() { - if (defined('LDAPAUTH_DNS_SITES_AND_SERVICES')) { - $connection = NULL; - $ldap_servers = ldapauth_get_ad_servers_for_site(); - foreach ($ldap_servers as $ldap_server) { - $ldap_address = LDAPAUTH_HOST . $ldap_server; - try { - $temp_conn = ldap_connect($ldap_address, LDAPAUTH_PORT); # ldap_connect doesn't actually connect it just checks for plausiable parameters. Only ldap_bind connects - if ($temp_conn) { - $connection = $temp_conn; - break; - } else { - error_log('Warning, unable to connect to: ' . $ldap_address . ' on port ' . LDAPAUTH_PORT . '. ' . ldap_error($temp_conn)); - } - } catch (Exception $e) { - error_log('Warning, unable to connect to: ' . $ldap_address . ' on port ' . LDAPAUTH_PORT . '. ' . __FILE__, __FUNCTION__,$e->getMessage()); - } - } + if (defined('LDAPAUTH_DNS_SITES_AND_SERVICES')) { + $connection = NULL; + $ldap_servers = ldapauth_get_ad_servers_for_site(); + foreach ($ldap_servers as $ldap_server) { + $ldap_address = LDAPAUTH_HOST . $ldap_server; + try { + $temp_conn = ldap_connect($ldap_address, LDAPAUTH_PORT); # ldap_connect doesn't actually connect it just checks for plausiable parameters. Only ldap_bind connects + if ($temp_conn) { + $connection = $temp_conn; + break; + } else { + error_log('Warning, unable to connect to: ' . $ldap_address . ' on port ' . LDAPAUTH_PORT . '. ' . ldap_error($temp_conn)); + } + } catch (Exception $e) { + error_log('Warning, unable to connect to: ' . $ldap_address . ' on port ' . LDAPAUTH_PORT . '. ' . __FILE__, __FUNCTION__,$e->getMessage()); + } + } - if ($connection) { - return $connection; - } else { - die("Cannot connect to LDAP for site and service " . LDAPAUTH_DNS_SITES_AND_SERVICES); - } + if ($connection) { + return $connection; + } else { + die("Cannot connect to LDAP for site and service " . LDAPAUTH_DNS_SITES_AND_SERVICES); + } - } else { - return ldap_connect(LDAPAUTH_HOST, LDAPAUTH_PORT); - } + } else { + return ldap_connect(LDAPAUTH_HOST, LDAPAUTH_PORT); + } } // returns true/false @@ -182,19 +181,26 @@ function ldapauth_is_valid_user( $value ) { } } + // Check if using LDAP Filter, otherwise, filter by LDAPAUTH_USERNAME_FIELD only. + if ( !defined('LDAPAUTH_SEARCH_FILTER') ){ + $ldapFilter = LDAPAUTH_USERNAME_FIELD . "=" . $_REQUEST['username']; + } else { + $ldapFilter = sprintf(LDAPAUTH_SEARCH_FILTER, $_REQUEST['username']); + } + // Limit the attrs to the ones we need $attrs = array('dn', LDAPAUTH_USERNAME_FIELD); if (defined('LDAPAUTH_GROUP_ATTR')) array_push($attrs, LDAPAUTH_GROUP_ATTR); - $searchDn = ldap_search($ldapConnection, LDAPAUTH_BASE, LDAPAUTH_USERNAME_FIELD . "=" . $_REQUEST['username'], $attrs ); + $searchDn = ldap_search($ldapConnection, LDAPAUTH_BASE, $ldapFilter, $attrs ); if (!$searchDn) return $value; $searchResult = ldap_get_entries($ldapConnection, $searchDn); if (!$searchResult) return $value; $userDn = $searchResult[0]['dn']; if (!$userDn && !$ldapSuccess) return $value; if (empty($ldapSuccess)) { // we don't need to do this if we already bound using username and LDAPAUTH_BIND_WITH_USER_TEMPLATE - $ldapSuccess = @ldap_bind($ldapConnection, $userDn, $_REQUEST['password']); + $ldapSuccess = @ldap_bind($ldapConnection, $userDn, $_REQUEST['password']); } // success? @@ -208,7 +214,7 @@ function ldapauth_is_valid_user( $value ) { $groups_to_check = explode(";", strtolower(LDAPAUTH_GROUP_REQ)); // This is now an array foreach($searchResult[0][LDAPAUTH_GROUP_ATTR] as $grps) { - if (in_array(strtolower($grps), $groups_to_check)) { $in_group = true; break; } + if (in_array(strtolower($grps), $groups_to_check)) { $in_group = true; break; } } if (!$in_group) die('Not in admin group'); @@ -263,8 +269,8 @@ yourls_add_action( 'logout', 'ldapauth_logout_hook' ); function ldapauth_logout_hook( $args ) { if (!defined(LDAPAUTH_USERCACHE_TYPE)) { - unset($_SESSION['LDAPAUTH_AUTH_USER']); - setcookie('PHPSESSID', '', 0, '/'); + unset($_SESSION['LDAPAUTH_AUTH_USER']); + setcookie('PHPSESSID', '', 0, '/'); } }