-
Notifications
You must be signed in to change notification settings - Fork 13
/
LDAPAuthenticator.php
239 lines (208 loc) · 7.85 KB
/
LDAPAuthenticator.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
<?php
namespace SilverStripe\LDAP\Authenticators;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Email\Email;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\LDAP\Forms\LDAPLoginForm;
use SilverStripe\LDAP\Services\LDAPService;
use SilverStripe\ORM\ValidationResult;
use SilverStripe\Security\Authenticator;
use SilverStripe\Security\Member;
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;
use Laminas\Authentication\Result;
/**
* Class LDAPAuthenticator
*
* Authenticate a user against LDAP, without the single sign-on component.
*/
class LDAPAuthenticator extends MemberAuthenticator
{
/**
* @var string
*/
private $name = 'LDAP';
/**
* Set to 'yes' to indicate if this module should look up usernames in LDAP by matching the email addresses.
*
* CAVEAT #1: only set to 'yes' for systems that enforce email uniqueness.
* Otherwise only the first LDAP user with matching email will be accessible.
*
* CAVEAT #2: this is untested for systems that use LDAP with principal style usernames (i.e. foo@bar.com).
* The system will misunderstand emails for usernames with uncertain outcome.
*
* @var string 'no' or 'yes'
*/
private static $allow_email_login = 'no';
/**
* Set to 'yes' to fallback login attempts to {@link $fallback_authenticator}.
* This will occur if LDAP fails to authenticate the user.
*
* @var string 'no' or 'yes'
*/
private static $fallback_authenticator = 'no';
/**
* The class of {@link Authenticator} to use as the fallback authenticator.
*
* @var string
*/
private static $fallback_authenticator_class = MemberAuthenticator::class;
/**
* @return string
*/
public static function get_name()
{
return Config::inst()->get(self::class, 'name');
}
/**
* @param Controller $controller
* @return LDAPLoginForm
*/
public static function get_login_form(Controller $controller)
{
return LDAPLoginForm::create($controller, LDAPAuthenticator::class, 'LoginForm');
}
/**
* Performs the login, but will also create and sync the Member record on-the-fly, if not found.
*
* @param array $data
* @param HTTPRequest $request
* @param ValidationResult|null $result
* @return null|Member
*/
public function authenticate(array $data, HTTPRequest $request, ValidationResult &$result = null)
{
$result = $result ?: ValidationResult::create();
$service = Injector::inst()->get(LDAPService::class);
$login = trim($data['Login'] ?? '');
if (Email::is_valid_address($login)) {
if (Config::inst()->get(self::class, 'allow_email_login') != 'yes') {
$result->addError(
_t(
__CLASS__ . '.PLEASEUSEUSERNAME',
'Please enter your username instead of your email to log in.'
)
);
return null;
}
$username = $service->getUsernameByEmail($login);
// No user found with this email.
if (!$username) {
if (Config::inst()->get(self::class, 'fallback_authenticator') === 'yes') {
if ($fallbackMember = $this->fallbackAuthenticate($data, $request)) {
{
return $fallbackMember;
}
}
}
$result->addError(_t(__CLASS__ . '.INVALIDCREDENTIALS', 'Invalid credentials'));
return null;
}
} else {
$username = $login;
}
$serviceAuthenticationResult = $service->authenticate($username, $data['Password']);
$success = $serviceAuthenticationResult['success'] === true;
if (!$success) {
/*
* Try the fallback method if admin or it failed for anything other than invalid credentials
* This is to avoid having an unhandled exception error thrown by PasswordEncryptor::create_for_algorithm()
*/
if (Config::inst()->get(self::class, 'fallback_authenticator') === 'yes') {
if (!in_array($serviceAuthenticationResult['code'], [Result::FAILURE_CREDENTIAL_INVALID])
|| $username === 'admin'
) {
if ($fallbackMember = $this->fallbackAuthenticate($data, $request)) {
return $fallbackMember;
}
}
}
$result->addError($serviceAuthenticationResult['message']);
return null;
}
$data = $service->getUserByUsername($serviceAuthenticationResult['identity']);
if (!$data) {
$result->addError(
_t(
__CLASS__ . '.PROBLEMFINDINGDATA',
'There was a problem retrieving your user data'
)
);
return null;
}
// LDAPMemberExtension::afterMemberLoggedIn() will update any other AD attributes mapped to Member fields
$member = Member::get()->filter('GUID', $data['objectguid'])->limit(1)->first();
if (!($member && $member->exists())) {
$member = new Member();
$member->GUID = $data['objectguid'];
}
// Update the users from LDAP so we are sure that the email is correct.
// This will also write the Member record.
$service->updateMemberFromLDAP($member, $data);
$request->getSession()->clear('BackURL');
return $member;
}
/**
* Try to authenticate using the fallback authenticator.
*
* @param array $data
* @param HTTPRequest $request
* @return null|Member
*/
protected function fallbackAuthenticate($data, HTTPRequest $request)
{
// Set Email from Login
if (array_key_exists('Login', $data ?? []) && !array_key_exists('Email', $data ?? [])) {
$data['Email'] = $data['Login'];
}
$authenticatorClass = Config::inst()->get(self::class, 'fallback_authenticator_class');
if ($authenticator = Injector::inst()->get($authenticatorClass)) {
$result = call_user_func(
[
$authenticator,
'authenticate'
],
$data,
$request
);
return $result;
}
}
public function getLoginHandler($link)
{
return LDAPLoginHandler::create($link, $this);
}
public function supportedServices()
{
$result = Authenticator::LOGIN | Authenticator::LOGOUT | Authenticator::CHECK_PASSWORD;
if ((bool)LDAPService::config()->get('allow_password_change')) {
$result |= Authenticator::RESET_PASSWORD | Authenticator::CHANGE_PASSWORD;
}
return $result;
}
public function getLostPasswordHandler($link)
{
return LDAPLostPasswordHandler::create($link, $this);
}
/**
* @param string $link
* @return LDAPChangePasswordHandler
*/
public function getChangePasswordHandler($link)
{
return LDAPChangePasswordHandler::create($link, $this);
}
public function checkPassword(Member $member, $password, ValidationResult &$result = null)
{
$result = $result ?: ValidationResult::create();
$service = Injector::inst()->get(LDAPService::class);
// Support email or username
$handle = Config::inst()->get(self::class, 'allow_email_login') === 'yes' ? 'Email' : 'Username';
$ldapResult = $service->authenticate($member->{$handle}, $password);
if (empty($ldapResult['success'])) {
$result->addError($ldapResult['message']);
}
return $result;
}
}