foglcz/ldap-authenticator
Composer 安装命令:
composer require foglcz/ldap-authenticator
包简介
LDAP Authenticator plugin for Nette Framework
关键字:
README 文档
README
The LDAP/Active Directory authenticator plugin for Nette Framework.
Supported out of the box:
- LDAP Authentication (obviously)
- User information loading, including user picture in AD
- User groups membership, including group inheritance. (groupA member of groupB, which is member of groupC? No problem!)
- Groups whitelist for login (aka "allowLogin")
- Groups blacklist for login (aka "refuseLogin")
- Admin role appender - user will have "admin" role, if he's present in some of the specified LDAP groups
- Groups-to-roles mapping based on group DN, name and/or e-mail address
- Post-processing data before identity creation based on Callbacks
- used for loading of user database id and more
- Identity generator from loaded data, based on callback - don't like default behaviour? Rewrite it!
- Username generator based on callback - following logins are valid by default:
- name.surname
- name.surname@yourdomain.com
- Success login handlers for integrating the LDAP into your application. In example, this is used for both userdata loading from LDAP (thumbnails, name & surname, etc.), and for loading the ID from database (so that your database model can work seamlessly with LDAP.)
Include once, extend forever. Under MIT license.
Install
$ composer require foglcz/ldap-authenticator
Use
-
Open
app/model/UserManager.phpand remove theimplements Nette\Security\IAuthenticator -
Open
app/config/config.neonfile and change defaultUserManagerdefinition.services: - App\Model\UserManagerbecomes:
services: userManager: App\Model\UserManager -
Open
app/config/config.neonfile and add following toservicesandparameterssections:parameters: ldap: hostname: 'xxx.xxx.xxx.xxx' # your LDAP server ip port: 3268 # your LDAP server port (if different than default) baseDn: 'DC=domain,DC=local' # your LDAP base DN search - usually change this to your domain.tld loadGroups: true # set to false if you don't want the auto groups loading & roles loading services: authenticator: class: foglcz\ldap\Authenticator(%ldap%, "yourcompany.com", "yourcomany.local") # Third parameter optional, used if you have different e-mail domains than the AD domain. setup: - addSuccessHandler('id', [@userManager, 'getAuthId']) -
Add following method into your
UserManagerclass:/** * Success handler for LDAP authenticator - loads the ID in database * * @param \Toyota\Component\Ldap\Core\Manager $ldap * @param array $userData * @throws Security\AuthenticationException * @return int */ public function getAuthId(\Toyota\Component\Ldap\Core\Manager $ldap, array $userData) { $username = 'ldap/' . $userData['username']; if($user = $this->database->table('user')->where('username = ?', $username)->fetch()) { return $user->id; } return $this->database->table('user')->insert(array('username' => $username)); }
The getAuthId function returns id of the identity, that gets generated - therefore, in rest of your application, you can freely use
$user->idfor relations etc.
Authentication flow
Essentially, the whole LDAP Authenticator is built on top of callbacks. This means that in most cases, you don't need to extend the class and/or make use of it in your authenticator. Instead, you'd "plug-in" your callbacks to do the work as it's needed in your particular project.
The authentication flow is as follows:
-
$user->login(username, password); -
Post-process the given username. By default, we strip out the domain parameter (see below) of the constructor, and replace it with FQDN. Therefore, you can have "yourdomain.local" Active Directory forrest, while logging in with the "email@yourdomain.com" usernames - or just with "email" part of the login. See Username generator for more details
-
Connect to LDAP server as specified in the configuration - the library tiesa/ldap is used for this purpose
-
Bind the given username to the domain, effectively authenticating against the LDAP. User is bind in format of username@fqdn , so in your case it might be username@yourdomain.local
-
If no exception is thrown, $userData array is created with following attributes:
array (2) username => "name.surname@yourdomain.local" (6) fqdn => "yourdomain.local" (14) -
Loop through success handlers. By default this in-loads User Data information from LDAP, and in-loads the groups memberships. Group inheritance is supported, so if user is a part of groupA, which is a part of groupB, both will show up in the memberships.
Note that here, your registered callbacks are called as well:
array (8) username => "name.surname@yourdomain.local" (6) fqdn => "yourdomain.local" (14) userinfo => array (10) lastName => "Surname | COMPANY" (17) firstName => "Name" (5) department => "IT" (2) company => "Yourcompany" (15) proxyAddresses => array (2) 0 => "SMTP:name.surname@yourcompany.com" (30) 1 => "smtp:nsurname@yourdomain.com" (25) fullName => "Name Surname | COMPANY" (23) changePasswordOnLogon => "000000000000000000" (18) UPN => "name.surname@yourdomain.local" (27) mail => "name.surname@yourdomain.com" (25) manager => "CN=Some One,OU=CS,OU=SBSUsers,OU=Users,OU=MyBusiness,DC=yourdomain,DC=local" (85) memberOf => array (16) "CN=DL Decision makers,OU=Distribution Groups,OU=MyBusiness,DC=yourdomain,DC=local" => array (3) dn => "CN=DL Decision makers,OU=Distribution Groups,OU=MyBusiness,DC=yourdomain,DC=local" (82) name => "DL Decision makers" (21) mail => "decisions" (12) -
Check whether the user is in any of the groups that are either allowed or refused to login. Throw exception when user is not allowed to login.
-
Call identity provider and give back the identity returned. For definition, see Identity generator section.
Most of these are built-in, enabled and disabled by altering the configuration (see below.)
Configuration options
Configuration is done within config.neon file. By default, the authenticator is pretty extensible by configuration.
parameters:
ldap:
hostname: 'xxx.xxx.xxx.xxx'
port: 3268
baseDn: 'DC=yourcompany,DC=com' # Base searchpath within your LDAP
loadGroups: true # By default on, turn off if you don't want to load groups
refuseLogin: ['DL USA'] # Either group FQDN's, names and/or e-mail addresses
allowLogin: ['DL VPN'] # Either group FQDN's, names and/or e-mail addresses
loadRolesAsMailGroups: true # By default off (false/not defined). If on, the group e-mails will be used as roles of the given user
rolesMap: # ldapGroup => roleName array mapping; will take either group FQDN, name and/or e-mail addr.
VPN_FG: vpn # If a user is present in such group, the latter value will be used as a user role by identity generator.
"DL Property": "property"
adminGroups: ['VPN_FG', 'Administrators'] # add "admin" role if present in the list of roles. Groups can be specified as FQDN's, names and/or e-mail addresses.
Allow / refuse logic
The authenticator employs refuse-first authentication principle. If you define both allow login & refuse login, members of refuse groups will be always refused, regardless of whether they are member of allowed groups.
If you define only refuseLogin parameter, all users will be logged in unless they are member of refused groups.
If you define only allowLogin parameter, all users that are not members of at least one of the allowed groups, will be
refused.
Exceptions
Authenticator throws following exceptions:
-
class UserInRefuseGroupException extends \foglcz\LDAP\AuthenticationExceptionThrown when user is a member ofrefuseLogingroups. Default message is "Members of XXX are not allowed to login." -
class UserNotInAllowedGroupException extends \foglcz\LDAP\AuthenticationExceptionThrown when you defineallowLoginparameter, and the user is not in any of the specified groups. Note that if you also defined refuseLogin groups, this gets thrown only when the user is not a member of those refuse groups (we employ refuse-first policy.) The message is: "You are not member of allowed groups that can login." -
class PossibleConfigurationErrorException extends AuthenticationExceptionThrown only when you forcefully turn off loadGroups parameter, but still employ functionality, which is dependant on the groups loading. These are allowLogin, refuseLogin, adminGroups, loadRolesAsMailGroups and rolesMap. Note: The exception is not thrown when you turn off groups loading, but set your ownmemberOfsuccess handler. -
class AuthenticationException extends \Nette\Security\AuthenticationExceptionThrown when user is, in fact, not allowed to login (the bind against LDAP failed.) Note that we use simple "Username or password is not valid" message.
Note that we throw our AuthenticationException, since we want to make it easy to catch LDAP errors in case you have
multiple authenticators like we do.
You can implement try {} catch {} for rewriting the error messages based on context. If you want to catch simply all
of them, feel free to catch \Nette\Security\AuthenticationException directly.
Callback options
As you can see from the authentication flow, the LdapAuthenticator is very extensible by default, thanks to callbacks.
The callbacks setup is done via class functions, so that it's easy to configure in config.neon as you can see below:
authenticator:
class: foglcz\ldap\Authenticator(%ldap%, "comodo.com", "comodo.local")
setup:
- setUsernameGenerator([@userModel, 'authGetUsername'])
- removeSuccessHandler('memberof')
- addSuccessHandler('memberof', [@userModel, 'getMemberOf'])
- addSuccessHandler('thumbnail', [\foglcz\LDAP\Success\ThumbnailLoader, 'getThumbnail'])
- setIdentityGenerator([@userModel, 'authGenerateIdentity'])
Success handlers
Success handlers are used to in-load more information to the $userData array. The userData is then used within
Identity generator, as a third parameter of generated identity.
Default success handler is described in top section and returns data, which will be saved within $userData
under specified key. Registration function takes two parameters:
- Key under which to store function result
- Callback which is called with parameters
Managerand$userData.
The success handlers constains no magic, and can be used for effectively anything you want.
Identity generator
The identity generator is used to generate an \Nette\Security\Identity class from the given $userData, which can
be extended by SuccessHandlers as seen above. The default identity generator looks up presence of $userData['memberOf']
parameter, to in-load the roles. The default identity generator also appends admin role if user is present within
admin groups as defined in config.
Piece of sourcecode is worth a thousand words. Default identity generator looks like this:
public function createIdentity(Toyota\Component\Ldap\Core\Manager $ldap, array $userData) { $roles = array(); if(!isset($userData['memberOf'])) { throw new PossibleConfigurationErrorException('User\'s memberOf index is not set; maybe reset default groups handler?'); } // Load roles based on membership if needed if(isset($this->config['loadRolesAsMailGroups'])) { $roles = array_merge($roles, $this->loadRoleMailsFromGroups($userData)); } // Load roles mapping if set if(isset($this->config['rolesMap'])) { $roles = array_merge($roles, $this->loadRoleMapping($userData)); } // In-load admin groups if requested if(isset($this->config['adminGroups']) && !in_array('admin', $roles) && $this->loadIsAdminMember($userData)) { $roles[] = 'admin'; } // Create identity & return return new Identity(isset($userData['id']) ? $userData['id'] : $userData['username'], $roles, $userData); }
Username generator
The username generator takes any user-supplied input and transforms it into LDAP-valid username. In general, we have two scenarios which we can find in the wild:
- The AD Forrest name is same as user e-mail domain (forrest name is
yourcompany.com) - The AD Forrest name is different than user e-mail domain (forrest name is
yourcompany.local)
There are also multiple ways how you can authenticate against LDAP:
- Using the NT-format
domain\username - Using the post-2000 FQDN format
username@domain.tld(that would be the forrest name, like yourdomain.local)
The authenticator employs second option - authentication with full FQDN - by default. This is also the reason for two parameters in the constructor:
class: foglcz\ldap\Authenticator(%ldap%, "<e-mail domain>", "<ldap domain>")
Default username generator works as follows:
- Check whether the user has supplied full e-mail -- checked against the second constructor
- If the user has indeed supplied the e-mail, remove the
@domain.compart - Append the
@domain.localif it has been supplied to the constructor.
Subsequent success handlers are using following query string to search for the user:
(|(userprincipalname=:upn:)(sAMAccountName=:username:))
That means, the success handlers search either for plain pre-2000 username or the user's UPN (which is the username@domain.local format). This has some interesting implications, as seen below.
Generator common usage
The default common usage of username generator would be to post-process given username into any format you'd like. For example, you'd like your people to use the default NT-format - thus returning the user supplied username by default.
OR you would like to use the pre-2000 usernames as login usernames, but not using the FQDN parts. For both, you can freely define any logic you want to, in username generator:
public function createUsername(Manager $ldap, $username) { return 'yourdomain\\' . $username; }
In order to use this generator, following should be added to setup part within config.neon:
authenticator:
class: foglcz\ldap\Authenticator(%ldap%, "whatever")
setup:
- setUsernameGenerator([@userModel, 'createUsername'])
Implications of the username generator rewrite
Default success handlers are using lookup via UPN or pre-2000 account name. This means, that the default success
handlers will strip-off any domain\ and @upn.local parts of the given username.
Extend with your class
There is little need to extend the default authenticator by yours, but thats entirely possible. You just need to
remember, that in config.neon services: section you can have only one Authenticator - that means that if you
extend the \foglcz\LDAP\Authenticator, you also need to remove it from your project's config.neon.
Contribute
Licensed under MIT license. Full text of license available in LICENSE.md file.
Feel free to fork! I grant write access to the repository to pull requests authors, if their changes makes sense for the project. I believe that with this approach, we can make sure that the pull request is highest quality, since it's always merged by the author of the pull request - not by the author of repository. Note: the repository write access is not revoked after merge.
Originally created by Pavel @foglcz Ptacek, (c) 2014
Full list of contributors can be seen in CONTRIBUTORS.md file.
foglcz/ldap-authenticator 适用场景与选型建议
foglcz/ldap-authenticator 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 7.34k 次下载、GitHub Stars 达 13, 最近一次更新时间为 2014 年 06 月 01 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「ldap」 「nette」 「auth」 「authenticator」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 foglcz/ldap-authenticator 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 foglcz/ldap-authenticator 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 foglcz/ldap-authenticator 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Laravel Multiauth package
LDAP user provider bundle for Symfony 6.4
A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.
This package provides a flexible way to add Role-based Permissions to Laravel 6.x
Nette Framework adapter for I18n package
Email Toolkit Plugin for CakePHP
统计信息
- 总下载量: 7.34k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 13
- 点击次数: 18
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: LGPL
- 更新时间: 2014-06-01