From 26904a0dbca3491c44d28828f76aaba75580942e Mon Sep 17 00:00:00 2001 From: K3A Date: Tue, 19 May 2015 18:23:35 +0200 Subject: [PATCH 01/11] Update README.md mentioned #1davoaust --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 73aed09..070c382 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Troubleshooting License ------- -Copyright 2013 K3A
+Copyright 2013 K3A, #1davoaust
Copyright 2013 Nicholas Waller (code@nicwaller.com) as I used some parts of his CAS authentication plugin :) This program is free software: you can redistribute it and/or modify From db7a45d839d7e0fc6c725ebff54d780d5e60ceb7 Mon Sep 17 00:00:00 2001 From: Jorrit Schippers Date: Fri, 15 Jul 2016 08:34:40 +0200 Subject: [PATCH 02/11] Fix README.md Changes a reference to yourls-cas-plugin to yourls-ldap-plugin and adds `;` to the define statements for easier copying. --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9714eef..d53e89b 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Installation Usage ----- -When yourls-cas-plugin is enabled and user was not successfuly authenticated using data specified in yourls_user_passwords, an LDAP authentication attempt will be made. If LDAP authentication is successful, then you will immediately go to the admin interface. +When yourls-ldap-plugin is enabled and user was not successfuly authenticated using data specified in yourls_user_passwords, an LDAP authentication attempt will be made. If LDAP authentication is successful, then you will immediately go to the admin interface. You can also set a privileged account to search the LDAP directory with. This is useful for directories that don't allow anonymous binding. @@ -21,21 +21,21 @@ Setting the groups settings will check the user is a member of that group before Configuration ------------- - * define( 'LDAPAUTH_HOST', 'ldaps://ldap.domain.com' ) // LDAP host name, IP or URL. You can use ldaps://host for LDAP with TLS - * define( 'LDAPAUTH_PORT', '636' ) // LDAP server port - often 389 or 636 for TLS (LDAPS) - * 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 + * define( 'LDAPAUTH_HOST', 'ldaps://ldap.domain.com' ); // LDAP host name, IP or URL. You can use ldaps://host for LDAP with TLS + * define( 'LDAPAUTH_PORT', '636' ); // LDAP server port - often 389 or 636 for TLS (LDAPS) + * 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: - * 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 + * 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 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 + * 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 automatically add LDAP users to config.php: - * define( 'LDAPAUTH_ADD_NEW', true ) // (optional) Add LDAP users to config.php + * define( 'LDAPAUTH_ADD_NEW', true ); // (optional) Add LDAP users to config.php NOTE: This will require config.php to be writable by your webserver user Troubleshooting From a7fe07614abcb5c979c54e1cd293154151ab0fef Mon Sep 17 00:00:00 2001 From: Chris Hastie Date: Thu, 21 Jul 2016 22:26:48 +0100 Subject: [PATCH 03/11] Add cache of LDAP users, stored as option in YOURLS database. Add ability to bind using current user (useful for AD/Samba) --- plugin.php | 75 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/plugin.php b/plugin.php index 26285ba..6e19edd 100644 --- a/plugin.php +++ b/plugin.php @@ -57,21 +57,35 @@ yourls_add_filter( 'is_valid_user', 'ldapauth_is_valid_user' ); // returns true/false function ldapauth_is_valid_user( $value ) { - // doesn't work for API... - if (yourls_is_API()) + global $yourls_user_passwords; + global $ydb; + + $ldapauth_usercache = $ydb->option['ldapauth_usercache']; + + // no point in continuing if the user has already been validated by core + if ($value) { + ldapauth_debug("Returning from ldapauth_is_valid_user as user is already validated"); return $value; + } + @session_start(); + // Always check & set early if ( !ldapauth_environment_check() ) { die( 'Invalid configuration for YOURLS LDAP plugin. Check PHP error log.' ); } + /* is the cookie needed anymore? Since the user cache is merged with $yourls_user_passwords + * core's yourls_check_auth_cookie should work. If so, a logged in user will arrive in this + * function with $value true, the function returns early and we never get here + */ if ( isset( $_SESSION['LDAPAUTH_AUTH_USER'] ) ) { // already authenticated... $username = $_SESSION['LDAPAUTH_AUTH_USER']; - if ( ldapauth_is_authorized_user( $username ) ) { + // why is this checked here, but not before the cookie is set? + if ( ldapauth_is_authorized_user( $username ) ) { yourls_set_user( $_SESSION['LDAPAUTH_AUTH_USER'] ); return true; } else { @@ -84,14 +98,24 @@ function ldapauth_is_valid_user( $value ) { $ldapConnection = ldap_connect(LDAPAUTH_HOST, LDAPAUTH_PORT); if (!$ldapConnection) die("Cannot connect to LDAP " . LDAPAUTH_HOST); ldap_set_option($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3); + //ldap_set_option($ldapConnection, LDAP_OPT_REFERRALS, 0); - // Check if using a privileged user account to search - if (defined('LDAPAUTH_SEARCH_USER') && defined('LDAPAUTH_SEARCH_PASS')) { + // should we to try and bind using the credentials being logged in with? + if (defined('LDAPAUTH_BIND_WITH_USER_TEMPLATE')) { + $bindRDN = sprintf(LDAPAUTH_BIND_WITH_USER_TEMPLATE, $_REQUEST['username']); + if (!($ldapSuccess = @ldap_bind($ldapConnection, $bindRDN, $_REQUEST['password']))) { + error_log('Couldn\'t bind to LDAP server with user ' . $bindRDN); + return $value; + } + } + + // Check if using a privileged user account to search - only if not already bound with current user + if (defined('LDAPAUTH_SEARCH_USER') && defined('LDAPAUTH_SEARCH_PASS') && empty($ldapSuccess)) { if (!@ldap_bind($ldapConnection, LDAPAUTH_SEARCH_USER, LDAPAUTH_SEARCH_PASS)) { die('Couldn\'t bind search user ' . LDAPAUTH_SEARCH_USER); } } - + // Limit the attrs to the ones we need $attrs = array('dn', LDAPAUTH_USERNAME_FIELD); if (defined('LDAPAUTH_GROUP_ATTR')) @@ -102,8 +126,10 @@ function ldapauth_is_valid_user( $value ) { $searchResult = ldap_get_entries($ldapConnection, $searchDn); if (!$searchResult) return $value; $userDn = $searchResult[0]['dn']; - if (!$userDn) return $value; - $ldapSuccess = @ldap_bind($ldapConnection, $userDn, $_REQUEST['password']); + 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']); + } @ldap_close($ldapConnection); // success? @@ -119,18 +145,25 @@ function ldapauth_is_valid_user( $value ) { foreach($searchResult[0][LDAPAUTH_GROUP_ATTR] as $grps) { if (in_array(strtolower($grps), $groups_to_check)) { $in_group = true; break; } } - if (!$in_group) die('Not in admin group'); } $username = $searchResult[0][LDAPAUTH_USERNAME_FIELD][0]; + if (empty($username)) { + // try with it lower cased + $username = $searchResult[0][strtolower(LDAPAUTH_USERNAME_FIELD)][0]; + } yourls_set_user($username); - global $yourls_user_passwords; if (LDAPAUTH_ADD_NEW && !array_key_exists($username, $yourls_user_passwords)) { ldapauth_create_user( $username, $_REQUEST['password'] ); } + // store the current user credentials in our cache. This cuts down calls to the LDAP + // server, and allows API keys to work with LDAP users + $ldapauth_usercache[$username] = 'phpass:' . ldapauth_hash_password($_REQUEST['password']); + yourls_update_option('ldapauth_usercache', $ldapauth_usercache); + $yourls_user_passwords[$username] = ldapauth_hash_password($_REQUEST['password']); $_SESSION['LDAPAUTH_AUTH_USER'] = $username; return true; @@ -166,6 +199,22 @@ function ldapauth_logout_hook( $args ) { setcookie('PHPSESSID', '', 0, '/'); } +/* This action, called as early as possible, retrieves our cache of LDAP users and + * merges it with $yourls_user_passwords. This enables core to do the authorisation + * of previously seen LDAP users, and also means that API signatures for LDAP users + * will work. Users that exist in both users/config.php and LDAP will need to use + * their LDAP passwords + */ +yourls_add_action ('plugins_loaded', 'ldapauth_merge_users'); +function ldapauth_merge_users() { + global $ydb; + global $yourls_user_passwords; + if(isset($ydb->option['ldapauth_usercache'])) { + ldapauth_debug("Merging text file users and cached LDAP users"); + $yourls_user_passwords = array_merge($yourls_user_passwords, $ydb->option['ldapauth_usercache']); + } +} + /** * Create user in config file * Code reused from yourls_hash_passwords_now() @@ -209,3 +258,9 @@ function ldapauth_hash_password ($password) { return $pass_hash; } + +function ldapauth_debug ($msg) { + if (defined('LDAPAUTH_DEBUG') && LDAPAUTH_DEBUG) { + error_log("yourls_ldap_auth: " . $msg); + } +} From 7c1f6be5011e4422e7a9f11c9c576839ee28b0c4 Mon Sep 17 00:00:00 2001 From: Chris Hastie Date: Fri, 22 Jul 2016 10:43:03 +0100 Subject: [PATCH 04/11] No need to try checking for LDAPAUTH_USERNAME_FIELD without lowercasing. PHP manual confirms array index is always lower case --- plugin.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugin.php b/plugin.php index 6e19edd..ab0666a 100644 --- a/plugin.php +++ b/plugin.php @@ -148,11 +148,8 @@ function ldapauth_is_valid_user( $value ) { if (!$in_group) die('Not in admin group'); } - $username = $searchResult[0][LDAPAUTH_USERNAME_FIELD][0]; - if (empty($username)) { - // try with it lower cased - $username = $searchResult[0][strtolower(LDAPAUTH_USERNAME_FIELD)][0]; - } + // attribute index returned by ldap_get_entries is lowercased (http://php.net/manual/en/function.ldap-get-entries.php) + $username = $searchResult[0][strtolower(LDAPAUTH_USERNAME_FIELD)][0]; yourls_set_user($username); if (LDAPAUTH_ADD_NEW && !array_key_exists($username, $yourls_user_passwords)) { From 25747fc14aa574c4a97d27bd5f6e559fe84e4f23 Mon Sep 17 00:00:00 2001 From: Chris Hastie Date: Fri, 22 Jul 2016 20:57:41 +0100 Subject: [PATCH 05/11] Make cache optional. Update README --- README.md | 43 +++++++++++++++++++++++++---------- plugin.php | 67 +++++++++++++++++++++++++++++++++++------------------- 2 files changed, 74 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 9714eef..1696eee 100644 --- a/README.md +++ b/README.md @@ -12,31 +12,39 @@ Installation Usage ----- -When yourls-cas-plugin is enabled and user was not successfuly authenticated using data specified in yourls_user_passwords, an LDAP authentication attempt will be made. If LDAP authentication is successful, then you will immediately go to the admin interface. +When yourls-ldap-plugin is enabled and user was not successfuly authenticated using data specified in yourls_user_passwords, an LDAP authentication attempt will be made. If LDAP authentication is successful, then you will immediately go to the admin interface. -You can also set a privileged account to search the LDAP directory with. This is useful for directories that don't allow anonymous binding. +You can also set a privileged account to search the LDAP directory with. This is useful for directories that don't allow anonymous binding. If you define a suitable template, the current user will be used binding. This is useful for Active Directory / Samba. Setting the groups settings will check the user is a member of that group before logging them in and storing their credentials. This check is only performed the first time they auth or when their password changes. +yourls-ldap-plugin by default will now implement a simple cache of LDAP users. As well as reducing requests to the LDAP server this has the effect of allowing YOURLS API to work with LDAP users. + Configuration ------------- - * define( 'LDAPAUTH_HOST', 'ldaps://ldap.domain.com' ) // LDAP host name, IP or URL. You can use ldaps://host for LDAP with TLS - * define( 'LDAPAUTH_PORT', '636' ) // LDAP server port - often 389 or 636 for TLS (LDAPS) - * 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 + * define( 'LDAPAUTH_HOST', 'ldaps://ldap.domain.com' ); // LDAP host name, IP or URL. You can use ldaps://host for LDAP with TLS + * define( 'LDAPAUTH_PORT', '636' ); // LDAP server port - often 389 or 636 for TLS (LDAPS) + * 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: - * 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 + * 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 + * 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: - * 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 + * 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 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: - * define( 'LDAPAUTH_ADD_NEW', true ) // (optional) Add LDAP users to config.php -NOTE: This will require config.php to be writable by your webserver user + * define( 'LDAPAUTH_ADD_NEW', true ); // (optional) Add LDAP users to config.php +NOTE: This will require config.php to be writable by your webserver user. This function is now largely unneeded because the database based cache offers similar benefits without the need to make config.php writeable. It is retained for backwards compatability Troubleshooting --------------- @@ -44,6 +52,17 @@ Troubleshooting * Check your webserver logs * You can try modifying plugin code to print some more debug info +About the user cache +-------------------- +When a successful login is made against an LDAP server the plugin will cache the username and encrypted password. Currently this is done by saving them in an array in YOURLS options table. This has some advantages: + + * It reduces requests to the LDAP server + * It means that users can still log in even if the LDAP server is unreachable + * It means that the YOURLS API can be used by LDAP users + +Unfortunately, the cache will not scale well. This is because it integrates tightly with YOURLS's internal auth mechanism, and that does not scale. If you have a few tens of LDAP users likely to use your YOURLS installation it should be fine. Much more than that and you may see performance issues. If so, you should probably disable the cache. This will mean +that your LDAP users will not be able to use the API. At least not unless they are listed in users/config.php, which suffers from the same scaling problems. + License ------- Copyright 2013 K3A, #1davoaust
diff --git a/plugin.php b/plugin.php index ab0666a..360b66a 100644 --- a/plugin.php +++ b/plugin.php @@ -40,6 +40,10 @@ function ldapauth_environment_check() { if ( !defined( 'LDAPAUTH_ADD_NEW' ) ) define( 'LDAPAUTH_ADD_NEW', false ); + + if ( !defined( 'LDAPAUTH_USERCACHE_TYPE' ) ) + define( 'LDAPAUTH_USERCACHE_TYPE', 1 ); + global $ldapauth_authorized_admins; if ( !isset( $ldapauth_authorized_admins ) ) { @@ -59,8 +63,15 @@ yourls_add_filter( 'is_valid_user', 'ldapauth_is_valid_user' ); function ldapauth_is_valid_user( $value ) { global $yourls_user_passwords; global $ydb; - - $ldapauth_usercache = $ydb->option['ldapauth_usercache']; + + // Always check & set early + if ( !ldapauth_environment_check() ) { + die( 'Invalid configuration for YOURLS LDAP plugin. Check PHP error log.' ); + } + + if( LDAPAUTH_USERCACHE_TYPE == 1) { + $ldapauth_usercache = $ydb->option['ldapauth_usercache']; + } // no point in continuing if the user has already been validated by core if ($value) { @@ -68,24 +79,23 @@ function ldapauth_is_valid_user( $value ) { return $value; } - - @session_start(); - - - // Always check & set early - if ( !ldapauth_environment_check() ) { - die( 'Invalid configuration for YOURLS LDAP plugin. Check PHP error log.' ); + // session is only needed if we don't use usercache + if (empty(LDAPAUTH_USERCACHE_TYPE)) { + @session_start(); } - /* is the cookie needed anymore? Since the user cache is merged with $yourls_user_passwords - * core's yourls_check_auth_cookie should work. If so, a logged in user will arrive in this - * function with $value true, the function returns early and we never get here - */ - if ( isset( $_SESSION['LDAPAUTH_AUTH_USER'] ) ) { + if ( empty(LDAPAUTH_USERCACHE_TYPE) && isset( $_SESSION['LDAPAUTH_AUTH_USER'] ) ) { // already authenticated... $username = $_SESSION['LDAPAUTH_AUTH_USER']; // why is this checked here, but not before the cookie is set? if ( ldapauth_is_authorized_user( $username ) ) { + if( !isset($yourls_user_passwords[$username]) ) { + // set a dummy password to work around the "Stealing cookies" problem + // we prepend with 'phpass:' to avoid YOURLS trying to auto-encrypt it and + // write it to user/config.php + ldapauth_debug('Setting dummy entry in $yourls_user_passwords for user ' . $username); + $yourls_user_passwords[$username]='phpass:ThereIsNoPasswordButHey,WhoCares?'; + } yourls_set_user( $_SESSION['LDAPAUTH_AUTH_USER'] ); return true; } else { @@ -156,13 +166,17 @@ function ldapauth_is_valid_user( $value ) { ldapauth_create_user( $username, $_REQUEST['password'] ); } - // store the current user credentials in our cache. This cuts down calls to the LDAP - // server, and allows API keys to work with LDAP users - $ldapauth_usercache[$username] = 'phpass:' . ldapauth_hash_password($_REQUEST['password']); - yourls_update_option('ldapauth_usercache', $ldapauth_usercache); - + if (LDAPAUTH_USERCACHE_TYPE == 1) { + // store the current user credentials in our cache. This cuts down calls to the LDAP + // server, and allows API keys to work with LDAP users + $ldapauth_usercache[$username] = 'phpass:' . ldapauth_hash_password($_REQUEST['password']); + yourls_update_option('ldapauth_usercache', $ldapauth_usercache); + } + $yourls_user_passwords[$username] = ldapauth_hash_password($_REQUEST['password']); - $_SESSION['LDAPAUTH_AUTH_USER'] = $username; + if (empty(LDAPAUTH_USERCACHE_TYPE)) { + $_SESSION['LDAPAUTH_AUTH_USER'] = $username; + } return true; } else { error_log("No LDAP success"); @@ -192,8 +206,10 @@ function ldapauth_is_authorized_user( $username ) { yourls_add_action( 'logout', 'ldapauth_logout_hook' ); function ldapauth_logout_hook( $args ) { - unset($_SESSION['LDAPAUTH_AUTH_USER']); - setcookie('PHPSESSID', '', 0, '/'); + if (empty(LDAPAUTH_USERCACHE_TYPE)) { + unset($_SESSION['LDAPAUTH_AUTH_USER']); + setcookie('PHPSESSID', '', 0, '/'); + } } /* This action, called as early as possible, retrieves our cache of LDAP users and @@ -206,9 +222,12 @@ yourls_add_action ('plugins_loaded', 'ldapauth_merge_users'); function ldapauth_merge_users() { global $ydb; global $yourls_user_passwords; - if(isset($ydb->option['ldapauth_usercache'])) { + if ( !ldapauth_environment_check() ) { + die( 'Invalid configuration for YOURLS LDAP plugin. Check PHP error log.' ); + } + if(LDAPAUTH_USERCACHE_TYPE==1 && isset($ydb->option['ldapauth_usercache'])) { ldapauth_debug("Merging text file users and cached LDAP users"); - $yourls_user_passwords = array_merge($yourls_user_passwords, $ydb->option['ldapauth_usercache']); + $yourls_user_passwords = array_merge($yourls_user_passwords, $ydb->option['ldapauth_usercache']); } } From 06112447d4d6922c1190be1927dfa047e198c3f5 Mon Sep 17 00:00:00 2001 From: Chris Hastie Date: Fri, 22 Jul 2016 21:03:46 +0100 Subject: [PATCH 06/11] Tidy README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1696eee..f150ab7 100644 --- a/README.md +++ b/README.md @@ -54,14 +54,14 @@ Troubleshooting About the user cache -------------------- -When a successful login is made against an LDAP server the plugin will cache the username and encrypted password. Currently this is done by saving them in an array in YOURLS options table. This has some advantages: +When a successful login is made against an LDAP server the plugin will cache the username and encrypted password. Currently this is done by saving them in an array in the YOURLS options table. This has some advantages: * It reduces requests to the LDAP server * It means that users can still log in even if the LDAP server is unreachable * It means that the YOURLS API can be used by LDAP users Unfortunately, the cache will not scale well. This is because it integrates tightly with YOURLS's internal auth mechanism, and that does not scale. If you have a few tens of LDAP users likely to use your YOURLS installation it should be fine. Much more than that and you may see performance issues. If so, you should probably disable the cache. This will mean -that your LDAP users will not be able to use the API. At least not unless they are listed in users/config.php, which suffers from the same scaling problems. +that your LDAP users will not be able to use the API. At least not unless they are also listed in users/config.php, which suffers from the same scaling problems. License ------- From 04d1464d6dfa79cf56581bf3853fd7903d1e274e Mon Sep 17 00:00:00 2001 From: K3A Date: Sat, 23 Jul 2016 14:39:40 +0200 Subject: [PATCH 07/11] merge fix / thanks tipichris --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2410c6f..f150ab7 100644 --- a/README.md +++ b/README.md @@ -32,22 +32,19 @@ 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 + * 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: * 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 automatically add LDAP users to config.php: - * define( 'LDAPAUTH_ADD_NEW', true ); // (optional) Add LDAP users to config.php -NOTE: This will require config.php to be writable by your webserver user -======= - 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: * define( 'LDAPAUTH_ADD_NEW', true ); // (optional) Add LDAP users to config.php NOTE: This will require config.php to be writable by your webserver user. This function is now largely unneeded because the database based cache offers similar benefits without the need to make config.php writeable. It is retained for backwards compatability ->>>>>>> 06112447d4d6922c1190be1927dfa047e198c3f5 Troubleshooting --------------- From 58479c3055cd5c46f698b6acfe4212c9ba4cab52 Mon Sep 17 00:00:00 2001 From: Henrique de Andrade Date: Fri, 18 Nov 2016 10:52:22 -0200 Subject: [PATCH 08/11] Update README.md adding scope search docs --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f150ab7..16459fc 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@ 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: + * 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: * 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 From 1c92562cb892a511e699e1df4ac6dd7980629f9d Mon Sep 17 00:00:00 2001 From: Henrique de Andrade Date: Fri, 18 Nov 2016 10:54:11 -0200 Subject: [PATCH 09/11] Update plugin.php Fixing the group authentication and adding scope option. --- plugin.php | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/plugin.php b/plugin.php index 360b66a..1763b65 100644 --- a/plugin.php +++ b/plugin.php @@ -12,6 +12,7 @@ Author URI: http://k3a.me // No direct call if( !defined( 'YOURLS_ABSPATH' ) ) die(); + // returns true if the environment is set up right function ldapauth_environment_check() { $required_params = array( @@ -140,22 +141,27 @@ function ldapauth_is_valid_user( $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']); } - @ldap_close($ldapConnection); - + // success? if ($ldapSuccess) { // are we checking group auth? if (defined('LDAPAUTH_GROUP_ATTR') && defined('LDAPAUTH_GROUP_REQ')) { - if (!array_key_exists(LDAPAUTH_GROUP_ATTR, $searchResult[0])) die('Not in any LDAP groups'); - - $in_group = false; - $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_group) die('Not in admin group'); + + $in_group = false; +$bind = ldap_bind($ldapConnection, LDAPAUTH_SEARCH_USER, LDAPAUTH_SEARCH_PASS); + + $searchGroup = ldap_search($ldapConnection, LDAPAUTH_GROUP_REQ, LDAPAUTH_GROUP_ATTR . "=" . $_REQUEST['username']); + $searchG = ldap_get_entries($ldapConnection,$searchGroup); + +if ( LDAPAUTH_GROUP_SCOP == 'base'){ + if ($searchG[0]['dn'] == LDAPAUTH_GROUP_REQ) $in_group = true; + } +else{ + if ($searchG[0]['dn']) $in_group = true; + } + +if (!$in_group) die('Not in admin group'); } // attribute index returned by ldap_get_entries is lowercased (http://php.net/manual/en/function.ldap-get-entries.php) @@ -218,6 +224,7 @@ function ldapauth_logout_hook( $args ) { * will work. Users that exist in both users/config.php and LDAP will need to use * their LDAP passwords */ + yourls_add_action ('plugins_loaded', 'ldapauth_merge_users'); function ldapauth_merge_users() { global $ydb; @@ -230,7 +237,6 @@ function ldapauth_merge_users() { $yourls_user_passwords = array_merge($yourls_user_passwords, $ydb->option['ldapauth_usercache']); } } - /** * Create user in config file * Code reused from yourls_hash_passwords_now() @@ -263,7 +269,6 @@ function ldapauth_create_user( $user, $new_password ) { return $pass_hash; } - /** * Hashes password the same way as yourls_hash_passwords_now() **/ @@ -274,7 +279,6 @@ function ldapauth_hash_password ($password) { return $pass_hash; } - function ldapauth_debug ($msg) { if (defined('LDAPAUTH_DEBUG') && LDAPAUTH_DEBUG) { error_log("yourls_ldap_auth: " . $msg); From 0950958232ddf116f1cd075d066096bc86eb4c4a Mon Sep 17 00:00:00 2001 From: Henrique de Andrade Date: Fri, 18 Nov 2016 15:55:56 -0200 Subject: [PATCH 10/11] Update plugin.php Adding again the validation that allow more than one admin group --- plugin.php | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/plugin.php b/plugin.php index 1763b65..cee8e19 100644 --- a/plugin.php +++ b/plugin.php @@ -149,19 +149,20 @@ function ldapauth_is_valid_user( $value ) { if (defined('LDAPAUTH_GROUP_ATTR') && defined('LDAPAUTH_GROUP_REQ')) { $in_group = false; -$bind = ldap_bind($ldapConnection, LDAPAUTH_SEARCH_USER, LDAPAUTH_SEARCH_PASS); + $bind = ldap_bind($ldapConnection, LDAPAUTH_SEARCH_USER, LDAPAUTH_SEARCH_PASS); - $searchGroup = ldap_search($ldapConnection, LDAPAUTH_GROUP_REQ, LDAPAUTH_GROUP_ATTR . "=" . $_REQUEST['username']); - $searchG = ldap_get_entries($ldapConnection,$searchGroup); - -if ( LDAPAUTH_GROUP_SCOP == 'base'){ - if ($searchG[0]['dn'] == LDAPAUTH_GROUP_REQ) $in_group = true; - } -else{ - if ($searchG[0]['dn']) $in_group = true; - } - -if (!$in_group) die('Not in admin group'); + $groups_to_check = explode(";", strtolower(LDAPAUTH_GROUP_REQ)); // This is now an array + foreach($groups_to_check as $group){ + $searchGroup = ldap_search($ldapConnection, $group, LDAPAUTH_GROUP_ATTR . "=" . $_REQUEST['username']); + $searchG = ldap_get_entries($ldapConnection,$searchGroup); + if ( LDAPAUTH_GROUP_SCOP == 'base'){ + if ($searchG[0]['dn'] == $group) $in_group = true; + } + else{ + if ($searchG[0]['dn']) $in_group = true; + } + } + if (!$in_group) die('Not in admin group'); } // attribute index returned by ldap_get_entries is lowercased (http://php.net/manual/en/function.ldap-get-entries.php) From 5a0e0dccfe34b579f9de706b5ce080b3d8491a7f Mon Sep 17 00:00:00 2001 From: K3A Date: Mon, 13 Feb 2017 22:10:52 +0100 Subject: [PATCH 11/11] spelling fix --- plugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.php b/plugin.php index cee8e19..6b55d9e 100644 --- a/plugin.php +++ b/plugin.php @@ -17,7 +17,7 @@ if( !defined( 'YOURLS_ABSPATH' ) ) die(); function ldapauth_environment_check() { $required_params = array( 'LDAPAUTH_HOST', // ldap host - //'LDAAUTHP_PORT', // ldap port + //'LDAPAUTH_PORT', // ldap port 'LDAPAUTH_BASE', // base ldap path //'LDAPAUTH_USERNAME_FIELD', // field to check the username against );