Completed
Pull Request — master (#7007)
by Simon
08:19
created

MemberAuthenticator   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 180
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 13

Importance

Changes 0
Metric Value
dl 0
loc 180
rs 10
c 0
b 0
f 0
wmc 27
lcom 0
cbo 13

8 Methods

Rating   Name   Duplication   Size   Complexity  
A supportedServices() 0 6 1
A authenticate() 0 14 3
C authenticateMember() 0 58 12
C recordLoginAttempt() 0 38 7
A getLostPasswordHandler() 0 4 1
A getChangePasswordHandler() 0 4 1
A getLoginHandler() 0 4 1
A getLogoutHandler() 0 4 1
1
<?php
2
3
namespace SilverStripe\Security\MemberAuthenticator;
4
5
use InvalidArgumentException;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Control\Session;
8
use SilverStripe\ORM\ValidationResult;
9
use SilverStripe\Security\Authenticator;
10
use SilverStripe\Security\LoginAttempt;
11
use SilverStripe\Security\Member;
12
use SilverStripe\Security\Security;
13
14
/**
15
 * Authenticator for the default "member" method
16
 *
17
 * @author Sam Minnee <[email protected]>
18
 * @author Simon Erkelens <[email protected]>
19
 */
20
class MemberAuthenticator implements Authenticator
21
{
22
23
    public function supportedServices()
24
    {
25
        // Bitwise-OR of all the supported services in this Authenticator, to make a bitmask
26
        return Authenticator::LOGIN | Authenticator::LOGOUT | Authenticator::CHANGE_PASSWORD
27
            | Authenticator::RESET_PASSWORD;
28
    }
29
30
    /**
31
     * @param array $data
32
     * @param null|ValidationResult $result
33
     * @return null|Member
34
     */
35
    public function authenticate($data, &$result = null)
36
    {
37
        // Find authenticated member
38
        $member = $this->authenticateMember($data, $result);
39
40
        // Optionally record every login attempt as a {@link LoginAttempt} object
41
        $this->recordLoginAttempt($data, $member, $result->isValid());
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...
42
43
        if ($member) {
44
            Session::clear('BackURL');
45
        }
46
47
        return $result->isValid() ? $member : null;
48
    }
49
50
    /**
51
     * Attempt to find and authenticate member if possible from the given data
52
     *
53
     * @param array $data Form submitted data
54
     * @param ValidationResult $result
55
     * @param Member|null This third parameter is used in the CMSAuthenticator(s)
56
     * @return Member Found member, regardless of successful login
57
     */
58
    protected function authenticateMember($data, &$result = null, $member = null)
59
    {
60
        // Default success to false
61
        $email = !empty($data['Email']) ? $data['Email'] : null;
62
        $result = new ValidationResult();
63
64
        // Check default login (see Security::setDefaultAdmin())
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
65
        $asDefaultAdmin = $email === Security::default_admin_username();
66
        if ($asDefaultAdmin) {
67
            // If logging is as default admin, ensure record is setup correctly
68
            $member = Member::default_admin();
69
            $success = Security::check_default_admin($email, $data['Password']);
70
            $result = $member->canLogIn();
71
            //protect against failed login
72
            if ($success && $result->isValid()) {
73
                return $member;
74
            } else {
75
                $result->addError(_t(
76
                    'SilverStripe\\Security\\Member.ERRORWRONGCRED',
77
                    "The provided details don't seem to be correct. Please try again."
78
                ));
79
            }
80
        }
81
82
        // Attempt to identify user by email
83
        if (!$member && $email) {
84
            // Find user by email
85
            /** @var Member $member */
86
            $member = Member::get()
87
                ->filter([Member::config()->get('unique_identifier_field') => $email])
88
                ->first();
89
        }
90
91
        // Validate against member if possible
92
        if ($member && !$asDefaultAdmin) {
93
            $result = $member->checkPassword($data['Password']);
94
        }
95
96
        // Emit failure to member and form (if available)
97
        if (!$result->isValid()) {
98
            if ($member) {
99
                $member->registerFailedLogin();
100
            }
101
        } else {
102
            if ($member) {
103
                $member->registerSuccessfulLogin();
104
            } else {
105
                // A non-existing member occurred. This will make the result "valid" so let's invalidate
106
                $result->addError(_t(
107
                    'SilverStripe\\Security\\Member.ERRORWRONGCRED',
108
                    "The provided details don't seem to be correct. Please try again."
109
                ));
110
                $member = null;
111
            }
112
        }
113
114
        return $member;
115
    }
116
117
    /**
118
     * Log login attempt
119
     * TODO We could handle this with an extension
120
     *
121
     * @param array $data
122
     * @param Member $member
123
     * @param boolean $success
124
     */
125
    protected function recordLoginAttempt($data, $member, $success)
126
    {
127
        if (!Security::config()->get('login_recording')) {
128
            return;
129
        }
130
131
        // Check email is valid
132
        /** @skipUpgrade */
133
        $email = isset($data['Email']) ? $data['Email'] : null;
134
        if (is_array($email)) {
135
            throw new InvalidArgumentException("Bad email passed to MemberAuthenticator::authenticate(): $email");
136
        }
137
138
        $attempt = LoginAttempt::create();
139
        if ($success && $member) {
140
            // successful login (member is existing with matching password)
141
            $attempt->MemberID = $member->ID;
142
            $attempt->Status = 'Success';
143
144
            // Audit logging hook
145
            $member->extend('authenticated');
146
        } else {
147
            // Failed login - we're trying to see if a user exists with this email (disregarding wrong passwords)
148
            $attempt->Status = 'Failure';
149
            if ($member) {
150
                // Audit logging hook
151
                $attempt->MemberID = $member->ID;
152
                $member->extend('authenticationFailed');
153
            } else {
154
                // Audit logging hook
155
                Member::singleton()->extend('authenticationFailedUnknownUser', $data);
156
            }
157
        }
158
159
        $attempt->Email = $email;
160
        $attempt->IP = Controller::curr()->getRequest()->getIP();
161
        $attempt->write();
162
    }
163
164
    /**
165
     * @param string $link
166
     * @return LostPasswordHandler
167
     */
168
    public function getLostPasswordHandler($link)
169
    {
170
        return LostPasswordHandler::create($link, $this);
171
    }
172
173
    /**
174
     * @param string $link
175
     * @return ChangePasswordHandler
176
     */
177
    public function getChangePasswordHandler($link)
178
    {
179
        return ChangePasswordHandler::create($link, $this);
180
    }
181
182
    /**
183
     * @param string $link
184
     * @return LoginHandler
185
     */
186
    public function getLoginHandler($link)
187
    {
188
        return LoginHandler::create($link, $this);
189
    }
190
191
    /**
192
     * @param string $link
193
     * @return LogoutHandler
194
     */
195
    public function getLogoutHandler($link)
196
    {
197
        return LogoutHandler::create($link, $this);
198
    }
199
}
200