Completed
Push — master ( e2b0c5...f862ce )
by Sam
08:22
created

MemberAuthenticator::get_name()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Security;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Control\Session;
7
use SilverStripe\Forms\Form;
8
use SilverStripe\ORM\ValidationResult;
9
use InvalidArgumentException;
10
11
/**
12
 * Authenticator for the default "member" method
13
 *
14
 * @author Markus Lanthaler <[email protected]>
15
 */
16
class MemberAuthenticator extends Authenticator
17
{
18
19
    /**
20
     * Contains encryption algorithm identifiers.
21
     * If set, will migrate to new precision-safe password hashing
22
     * upon login. See http://open.silverstripe.org/ticket/3004
23
     *
24
     * @var array
25
     */
26
    private static $migrate_legacy_hashes = array(
27
        'md5' => 'md5_v2.4',
28
        'sha1' => 'sha1_v2.4'
29
    );
30
31
    /**
32
     * Attempt to find and authenticate member if possible from the given data
33
     *
34
     * @param array $data
35
     * @param Form $form
36
     * @param bool &$success Success flag
37
     * @return Member Found member, regardless of successful login
38
     */
39
    protected static function authenticate_member($data, $form, &$success)
40
    {
41
        // Default success to false
42
        $success = false;
43
44
        // Attempt to identify by temporary ID
45
        $member = null;
46
        $email = null;
47
        if (!empty($data['tempid'])) {
48
            // Find user by tempid, in case they are re-validating an existing session
49
            $member = Member::member_from_tempid($data['tempid']);
50
            if ($member) {
51
                $email = $member->Email;
52
            }
53
        }
54
55
        // Otherwise, get email from posted value instead
56
        /** @skipUpgrade */
57
        if (!$member && !empty($data['Email'])) {
58
            $email = $data['Email'];
59
        }
60
61
        // 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...
62
        $asDefaultAdmin = $email === Security::default_admin_username();
63
        if ($asDefaultAdmin) {
64
            // If logging is as default admin, ensure record is setup correctly
65
            $member = Member::default_admin();
66
            $success = !$member->isLockedOut() && Security::check_default_admin($email, $data['Password']);
67
            //protect against failed login
68
            if ($success) {
69
                return $member;
70
            }
71
        }
72
73
        // Attempt to identify user by email
74
        if (!$member && $email) {
75
            // Find user by email
76
            $member = Member::get()
77
                ->filter(Member::config()->unique_identifier_field, $email)
78
                ->first();
79
        }
80
81
        // Validate against member if possible
82
        if ($member && !$asDefaultAdmin) {
83
            $result = $member->checkPassword($data['Password']);
84
            $success = $result->isValid();
85
        } else {
86
            $result = ValidationResult::create()->addError(_t(
87
                'Member.ERRORWRONGCRED',
88
                'The provided details don\'t seem to be correct. Please try again.'
89
            ));
90
        }
91
92
        // Emit failure to member and form (if available)
93
        if (!$success) {
94
            if ($member) {
95
                $member->registerFailedLogin();
96
            }
97
            if ($form) {
98
                $form->setSessionValidationResult($result, true);
99
            }
100
        } else {
101
            if ($member) {
102
                $member->registerSuccessfulLogin();
103
            }
104
        }
105
106
        return $member;
107
    }
108
109
    /**
110
     * Log login attempt
111
     * TODO We could handle this with an extension
112
     *
113
     * @param array $data
114
     * @param Member $member
115
     * @param bool $success
116
     */
117
    protected static function record_login_attempt($data, $member, $success)
118
    {
119
        if (!Security::config()->login_recording) {
120
            return;
121
        }
122
123
        // Check email is valid
124
        /** @skipUpgrade */
125
        $email = isset($data['Email']) ? $data['Email'] : null;
126
        if (is_array($email)) {
127
            throw new InvalidArgumentException("Bad email passed to MemberAuthenticator::authenticate(): $email");
128
        }
129
130
        $attempt = new LoginAttempt();
131
        if ($success) {
132
            // successful login (member is existing with matching password)
133
            $attempt->MemberID = $member->ID;
134
            $attempt->Status = 'Success';
135
136
            // Audit logging hook
137
            $member->extend('authenticated');
138
        } else {
139
            // Failed login - we're trying to see if a user exists with this email (disregarding wrong passwords)
140
            $attempt->Status = 'Failure';
141
            if ($member) {
142
                // Audit logging hook
143
                $attempt->MemberID = $member->ID;
144
                $member->extend('authenticationFailed');
145
            } else {
146
                // Audit logging hook
147
                Member::singleton()->extend('authenticationFailedUnknownUser', $data);
148
            }
149
        }
150
151
        $attempt->Email = $email;
152
        $attempt->IP = Controller::curr()->getRequest()->getIP();
153
        $attempt->write();
154
    }
155
156
    /**
157
     * Method to authenticate an user
158
     *
159
     * @param array $data Raw data to authenticate the user
160
     * @param Form $form Optional: If passed, better error messages can be
161
     *                             produced by using
162
     *                             {@link Form::sessionMessage()}
163
     * @return bool|Member Returns FALSE if authentication fails, otherwise
164
     *                     the member object
165
     * @see Security::setDefaultAdmin()
166
     */
167
    public static function authenticate($data, Form $form = null)
168
    {
169
        // Find authenticated member
170
        $member = static::authenticate_member($data, $form, $success);
0 ignored issues
show
Bug introduced by
It seems like $form defined by parameter $form on line 167 can be null; however, SilverStripe\Security\Me...::authenticate_member() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
171
172
        // Optionally record every login attempt as a {@link LoginAttempt} object
173
        static::record_login_attempt($data, $member, $success);
0 ignored issues
show
Bug introduced by
It seems like $member defined by static::authenticate_mem...$data, $form, $success) on line 170 can be null; however, SilverStripe\Security\Me...:record_login_attempt() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
174
175
        // Legacy migration to precision-safe password hashes.
176
        // A login-event with cleartext passwords is the only time
177
        // when we can rehash passwords to a different hashing algorithm,
178
        // bulk-migration doesn't work due to the nature of hashing.
179
        // See PasswordEncryptor_LegacyPHPHash class.
180
        if ($success && $member && isset(self::$migrate_legacy_hashes[$member->PasswordEncryption])) {
181
            $member->Password = $data['Password'];
182
            $member->PasswordEncryption = self::$migrate_legacy_hashes[$member->PasswordEncryption];
183
            $member->write();
184
        }
185
186
        if ($success) {
187
            Session::clear('BackURL');
188
        }
189
190
        return $success ? $member : null;
191
    }
192
193
194
    /**
195
     * Method that creates the login form for this authentication method
196
     *
197
     * @param Controller $controller The parent controller, necessary to create the
198
     *                   appropriate form action tag
199
     * @return Form Returns the login form to use with this authentication
200
     *              method
201
     */
202
    public static function get_login_form(Controller $controller)
203
    {
204
        /** @skipUpgrade */
205
        return MemberLoginForm::create($controller, self::class, "LoginForm");
206
    }
207
208
    public static function get_cms_login_form(Controller $controller)
209
    {
210
        /** @skipUpgrade */
211
        return CMSMemberLoginForm::create($controller, "LoginForm");
212
    }
213
214
    public static function supports_cms()
215
    {
216
        // Don't automatically support subclasses of MemberAuthenticator
217
        return get_called_class() === __CLASS__;
218
    }
219
}
220