1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Class LDAPAuthenticator. |
4
|
|
|
* |
5
|
|
|
* Authenticate a user against LDAP, without the single sign-on component. |
6
|
|
|
* |
7
|
|
|
* See SAMLAuthenticator for further information. |
8
|
|
|
*/ |
9
|
|
|
class LDAPAuthenticator extends Authenticator |
10
|
|
|
{ |
11
|
|
|
/** |
12
|
|
|
* @var string |
13
|
|
|
*/ |
14
|
|
|
private $name = 'LDAP'; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Set to 'yes' to indicate if this module should look up usernames in LDAP by matching the email addresses. |
18
|
|
|
* |
19
|
|
|
* CAVEAT #1: only set to 'yes' for systems that enforce email uniqueness. |
20
|
|
|
* Otherwise only the first LDAP user with matching email will be accessible. |
21
|
|
|
* |
22
|
|
|
* CAVEAT #2: this is untested for systems that use LDAP with principal style usernames (i.e. [email protected]). |
23
|
|
|
* The system will misunderstand emails for usernames with uncertain outcome. |
24
|
|
|
* |
25
|
|
|
* @var string 'no' or 'yes' |
26
|
|
|
*/ |
27
|
|
|
private static $allow_email_login = 'no'; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Set to 'yes' to fallback login attempts to {@link $fallback_authenticator}. |
31
|
|
|
* This will occur if LDAP fails to authenticate the user. |
32
|
|
|
* |
33
|
|
|
* @var string 'no' or 'yes' |
34
|
|
|
*/ |
35
|
|
|
private static $fallback_authenticator = 'no'; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* The class of {@link Authenticator} to use as the fallback authenticator. |
39
|
|
|
* |
40
|
|
|
* @var string |
41
|
|
|
*/ |
42
|
|
|
private static $fallback_authenticator_class = 'MemberAuthenticator'; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @return string |
46
|
|
|
*/ |
47
|
|
|
public static function get_name() |
48
|
|
|
{ |
49
|
|
|
return Config::inst()->get('LDAPAuthenticator', 'name'); |
|
|
|
|
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @param Controller $controller |
54
|
|
|
* |
55
|
|
|
* @return LDAPLoginForm |
56
|
|
|
*/ |
57
|
|
|
public static function get_login_form(Controller $controller) |
58
|
|
|
{ |
59
|
|
|
return new LDAPLoginForm($controller, 'LoginForm'); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Performs the login, but will also create and sync the Member record on-the-fly, if not found. |
64
|
|
|
* |
65
|
|
|
* @param array $data |
66
|
|
|
* @param Form $form |
67
|
|
|
* |
68
|
|
|
* @throws SS_HTTPResponse_Exception |
69
|
|
|
* |
70
|
|
|
* @return bool|Member|void |
71
|
|
|
*/ |
72
|
|
|
public static function authenticate($data, Form $form = null) |
73
|
|
|
{ |
74
|
|
|
/** @var LDAPService $service */ |
75
|
|
|
$service = Injector::inst()->get('LDAPService'); |
76
|
|
|
|
77
|
|
|
$login = trim($data['Login']); |
78
|
|
|
$username = $login; |
79
|
|
|
if (Email::is_valid_address($login)) { |
80
|
|
|
if (!self::allow_email_logins()) { |
81
|
|
|
self::form_error_msg($form, _t('LDAPAuthenticator.PLEASEUSEUSERNAME', 'Please enter your username instead of your email to log in.')); |
82
|
|
|
|
83
|
|
|
return; |
84
|
|
|
} |
85
|
|
|
$username = $service->getUsernameByEmail($login); |
86
|
|
|
|
87
|
|
|
if (!$username) { |
|
|
|
|
88
|
|
|
$fallbackMember = self::fallback_authenticate($data, $form); |
89
|
|
|
if ($fallbackMember) { |
90
|
|
|
return $fallbackMember; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
self::form_error_msg($form, _t('LDAPAuthenticator.INVALIDCREDENTIALS', 'Invalid credentials')); |
94
|
|
|
|
95
|
|
|
return; |
96
|
|
|
} |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
$result = $service->authenticate($username, $data['Password']); |
100
|
|
|
if (true !== $result['success']) { |
101
|
|
|
$fallbackMember = self::fallback_authenticate($data, $form); |
102
|
|
|
if ($fallbackMember) { |
103
|
|
|
return $fallbackMember; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
self::form_error_msg($form, $result['message']); |
107
|
|
|
|
108
|
|
|
return; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
$identity = $result['identity']; |
112
|
|
|
|
113
|
|
|
$member = self::get_member($identity); |
114
|
|
|
if (!$member) { |
115
|
|
|
self::form_error_msg($form, _t('LDAPAuthenticator.PROBLEMFINDINGDATA', 'There was a problem retrieving your user data')); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
Session::clear('BackURL'); |
119
|
|
|
|
120
|
|
|
return $member; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Try to authenticate using the fallback authenticator if enabled via config fallback_authenticator. |
125
|
|
|
* |
126
|
|
|
* @param array $data |
127
|
|
|
* @param Form|null $form |
128
|
|
|
* |
129
|
|
|
* @return Member|null |
130
|
|
|
*/ |
131
|
|
|
protected static function fallback_authenticate($data, Form $form = null) |
132
|
|
|
{ |
133
|
|
|
if ('yes' !== Config::inst()->get('LDAPAuthenticator', 'fallback_authenticator')) { |
134
|
|
|
return null; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
$authClass = Config::inst()->get('LDAPAuthenticator', 'fallback_authenticator_class'); |
138
|
|
|
|
139
|
|
|
SS_Log::log(sprintf('Using fallback authenticator "%s"', $authClass), SS_Log::DEBUG); |
140
|
|
|
|
141
|
|
|
return call_user_func( |
142
|
|
|
[$authClass, 'authenticate'], |
143
|
|
|
array_merge($data, ['Email' => $data['Login']]), |
144
|
|
|
$form |
145
|
|
|
); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
private static function form_error_msg($form, $message) |
149
|
|
|
{ |
150
|
|
|
if (!$form) { |
151
|
|
|
return; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
$form->sessionMessage($message, 'bad'); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* @return bool |
159
|
|
|
*/ |
160
|
|
|
private static function allow_email_logins() |
161
|
|
|
{ |
162
|
|
|
return 'yes' === Config::inst()->get('LDAPAuthenticator', 'allow_email_login'); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* @param string $identity |
167
|
|
|
* @return Member|null |
168
|
|
|
*/ |
169
|
|
|
private static function get_member($identity) |
170
|
|
|
{ |
171
|
|
|
/** @var LDAPService $service */ |
172
|
|
|
$service = Injector::inst()->get('LDAPService'); |
173
|
|
|
|
174
|
|
|
$data = $service->getUserByUsername($identity); |
175
|
|
|
if (!$data) { |
|
|
|
|
176
|
|
|
return null; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
// LDAPMemberExtension::memberLoggedIn() will update any other AD attributes mapped to Member fields |
180
|
|
|
/** @var Member $member */ |
181
|
|
|
$member = Member::get()->filter('GUID', $data['objectguid'])->limit(1)->first(); |
182
|
|
View Code Duplication |
if (!($member && $member->exists())) { |
|
|
|
|
183
|
|
|
$member = new Member(); |
184
|
|
|
$member->GUID = $data['objectguid']; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
// Update the users from LDAP so we are sure that the email is correct. |
188
|
|
|
// This will also write the Member record. |
189
|
|
|
$service->updateMemberFromLDAP($member); |
190
|
|
|
|
191
|
|
|
return $member; |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.