Completed
Pull Request — master (#116)
by Robbie
06:55
created

LDAPAuthenticator::supportedServices()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
3
namespace SilverStripe\ActiveDirectory\Authenticators;
4
5
use SilverStripe\ActiveDirectory\Services\LDAPService;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Control\Email\Email;
8
use SilverStripe\Control\HTTPRequest;
9
use SilverStripe\Control\Session;
10
use SilverStripe\Core\Config\Config;
11
use SilverStripe\Core\Injector\Injector;
12
use SilverStripe\ORM\ValidationResult;
13
use SilverStripe\Security\Authenticator;
14
use SilverStripe\Security\Member;
15
use SilverStripe\Security\MemberAuthenticator\LoginHandler;
16
use SilverStripe\Security\MemberAuthenticator\LogoutHandler;
17
18
/**
19
 * Class LDAPAuthenticator
20
 *
21
 * Authenticate a user against LDAP, without the single sign-on component.
22
 *
23
 * See SAMLAuthenticator for further information.
24
 *
25
 * @package activedirectory
26
 */
27
class LDAPAuthenticator implements Authenticator
28
{
29
    /**
30
     * @var string
31
     */
32
    private $name = 'LDAP';
0 ignored issues
show
Unused Code introduced by
The property $name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
33
34
    /**
35
     * Set to 'yes' to indicate if this module should look up usernames in LDAP by matching the email addresses.
36
     *
37
     * CAVEAT #1: only set to 'yes' for systems that enforce email uniqueness.
38
     * Otherwise only the first LDAP user with matching email will be accessible.
39
     *
40
     * CAVEAT #2: this is untested for systems that use LDAP with principal style usernames (i.e. [email protected]).
41
     * The system will misunderstand emails for usernames with uncertain outcome.
42
     *
43
     * @var string 'no' or 'yes'
44
     */
45
    private static $allow_email_login = 'no';
0 ignored issues
show
Unused Code introduced by
The property $allow_email_login is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
46
47
    /**
48
     * Set to 'yes' to fallback login attempts to {@link $fallback_authenticator}.
49
     * This will occur if LDAP fails to authenticate the user.
50
     *
51
     * @var string 'no' or 'yes'
52
     */
53
    private static $fallback_authenticator = 'no';
0 ignored issues
show
Unused Code introduced by
The property $fallback_authenticator is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
54
55
    /**
56
     * The class of {@link Authenticator} to use as the fallback authenticator.
57
     *
58
     * @var string
59
     */
60
    private static $fallback_authenticator_class = MemberAuthenticator::class;
0 ignored issues
show
Unused Code introduced by
The property $fallback_authenticator_class is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
61
62
    /**
63
     * @return string
64
     */
65
    public static function get_name()
66
    {
67
        return Config::inst()->get(self::class, 'name');
68
    }
69
70
    /**
71
     * @param Controller $controller
72
     * @return LDAPLoginForm
73
     */
74
    public static function get_login_form(Controller $controller)
75
    {
76
        return new LDAPLoginForm($controller, 'LoginForm');
0 ignored issues
show
Documentation introduced by
$controller is of type object<SilverStripe\Control\Controller>, but the function expects a object<SilverStripe\Acti...henticators\Controller>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
77
    }
78
79
    /**
80
     * Performs the login, but will also create and sync the Member record on-the-fly, if not found.
81
     *
82
     * @param array $data
83
     * @param HTTPRequest $request
84
     * @param ValidationResult|null $result
85
     * @return bool|Member
86
     * @internal param Form $form
87
     */
88
    public function authenticate(array $data, HTTPRequest $request, ValidationResult &$result = null)
89
    {
90
        $service = Injector::inst()->get(LDAPService::class);
91
        $login = trim($data['Login']);
92
        if (Email::is_valid_address($login)) {
93
            if (Config::inst()->get(self::class, 'allow_email_login') != 'yes') {
94
                $result->addError(
0 ignored issues
show
Bug introduced by
It seems like $result is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
95
                    _t(
96
                        'LDAPAuthenticator.PLEASEUSEUSERNAME',
97
                        'Please enter your username instead of your email to log in.'
98
                    )
99
                );
100
                return null;
101
            }
102
103
            $username = $service->getUsernameByEmail($login);
104
105
            // No user found with this email.
106 View Code Duplication
            if (!$username) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
                if (Config::inst()->get(self::class, 'fallback_authenticator') === 'yes') {
108
                    if ($fallbackMember = $this->fallback_authenticate($data, $request)) {
109
                        {
110
                            return $fallbackMember;
111
                        }
112
                    }
113
                }
114
115
                $result->addError(_t('LDAPAuthenticator.INVALIDCREDENTIALS', 'Invalid credentials'));
116
                return null;
117
            }
118
        } else {
119
            $username = $login;
120
        }
121
122
        $serviceAuthenticationResult = $service->authenticate($username, $data['Password']);
123
        $success = $serviceAuthenticationResult['success'] === true;
124 View Code Duplication
        if (!$success) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
125
            if (Config::inst()->get(self::class, 'fallback_authenticator') === 'yes') {
126
                $fallbackMember = $this->fallback_authenticate($data, $request);
127
                if ($fallbackMember) {
128
                    return $fallbackMember;
129
                }
130
            }
131
132
            $result->addError($result['message']);
133
134
            return null;
135
        }
136
137
        $data = $service->getUserByUsername($result['identity']);
138
        if (!$data) {
139
            $result->addError(
140
                _t(
141
                    'LDAPAuthenticator.PROBLEMFINDINGDATA',
142
                    'There was a problem retrieving your user data'
143
                )
144
            );
145
            return null;
146
        }
147
148
        // LDAPMemberExtension::memberLoggedIn() will update any other AD attributes mapped to Member fields
149
        $member = Member::get()->filter('GUID', $data['objectguid'])->limit(1)->first();
150 View Code Duplication
        if (!($member && $member->exists())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
151
            $member = new Member();
152
            $member->GUID = $data['objectguid'];
153
        }
154
155
        // Update the users from LDAP so we are sure that the email is correct.
156
        // This will also write the Member record.
157
        $service->updateMemberFromLDAP($member);
158
159
        $request->getSession()->clear('BackURL');
160
161
        return $member;
162
    }
163
164
    /**
165
     * Try to authenticate using the fallback authenticator.
166
     *
167
     * @param array $data
168
     * @param HTTPRequest $request
169
     * @return null|Member
170
     * @internal param null|Form $form
171
     */
172
    protected function fallback_authenticate($data, HTTPRequest $request)
173
    {
174
        $authenticatorClass = Config::inst()->get(self::class, 'fallback_authenticator_class');
175
        if ($authenticator = Injector::inst()->get($authenticatorClass)) {
176
            return call_user_func(
177
                [
178
                    $authenticator,
179
                    'authenticate'
180
                ],
181
                $data,
182
                $request
183
            );
184
        }
185
    }
186
187
    /**
188
     * Returns the services supported by this authenticator
189
     *
190
     * The number should be a bitwise-OR of 1 or more of the following constants:
191
     * Authenticator::LOGIN, Authenticator::LOGOUT, Authenticator::CHANGE_PASSWORD,
192
     * Authenticator::RESET_PASSWORD, or Authenticator::CMS_LOGIN
193
     *
194
     * @return int
195
     */
196
    public function supportedServices()
197
    {
198
        // TODO: Implement supportedServices() method.
199
    }
200
201
    /**
202
     * Return RequestHandler to manage the log-in process.
203
     *
204
     * The default URL of the RequestHandler should return the initial log-in form, any other
205
     * URL may be added for other steps & processing.
206
     *
207
     * URL-handling methods may return an array [ "Form" => (form-object) ] which can then
208
     * be merged into a default controller.
209
     *
210
     * @param string $link The base link to use for this RequestHandler
211
     * @return LoginHandler
212
     */
213
    public function getLoginHandler($link)
214
    {
215
        // TODO: Implement getLoginHandler() method.
216
    }
217
218
    /**
219
     * Return the RequestHandler to manage the log-out process.
220
     *
221
     * The default URL of the RequestHandler should log the user out immediately and destroy the session.
222
     *
223
     * @param string $link The base link to use for this RequestHandler
224
     * @return LogoutHandler
225
     */
226
    public function getLogOutHandler($link)
227
    {
228
        // TODO: Implement getLogOutHandler() method.
229
    }
230
231
    /**
232
     * Return RequestHandler to manage the change-password process.
233
     *
234
     * The default URL of the RequetHandler should return the initial change-password form,
235
     * any other URL may be added for other steps & processing.
236
     *
237
     * URL-handling methods may return an array [ "Form" => (form-object) ] which can then
238
     * be merged into a default controller.
239
     *
240
     * @param string $link The base link to use for this RequestHnadler
241
     */
242
    public function getChangePasswordHandler($link)
243
    {
244
        // TODO: Implement getChangePasswordHandler() method.
245
    }
246
247
    /**
248
     * @param string $link
249
     * @return mixed
250
     */
251
    public function getLostPasswordHandler($link)
252
    {
253
        // TODO: Implement getLostPasswordHandler() method.
254
    }
255
256
    /**
257
     * Check if the passed password matches the stored one (if the member is not locked out).
258
     *
259
     * Note, we don't return early, to prevent differences in timings to give away if a member
260
     * password is invalid.
261
     *
262
     * @param Member $member
263
     * @param string $password
264
     * @param ValidationResult $result
265
     * @return ValidationResult
266
     */
267
    public function checkPassword(Member $member, $password, ValidationResult &$result = null)
268
    {
269
        // TODO: Implement checkPassword() method.
270
    }
271
}
272