Completed
Push — 3.4 ( 9c2b9d...cf8f78 )
by Daniel
13:20
created

MemberAuthenticator::authenticate_member()   F

Complexity

Conditions 17
Paths 654

Size

Total Lines 64
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 37
nc 654
nop 3
dl 0
loc 64
rs 3.125
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Authenticator for the default "member" method
4
 *
5
 * @author Markus Lanthaler <[email protected]>
6
 * @package framework
7
 * @subpackage security
8
 */
9
class MemberAuthenticator extends Authenticator {
10
11
	/**
12
	 * Contains encryption algorithm identifiers.
13
	 * If set, will migrate to new precision-safe password hashing
14
	 * upon login. See http://open.silverstripe.org/ticket/3004
15
	 *
16
	 * @var array
17
	 */
18
	private static $migrate_legacy_hashes = array(
19
		'md5' => 'md5_v2.4',
20
		'sha1' => 'sha1_v2.4'
21
	);
22
23
	/**
24
	 * Attempt to find and authenticate member if possible from the given data
25
	 *
26
	 * @param array $data
27
	 * @param Form $form
28
	 * @param bool &$success Success flag
29
	 * @return Member Found member, regardless of successful login
30
	 */
31
	protected static function authenticate_member($data, $form, &$success) {
32
		// Default success to false
33
		$success = false;
34
35
		// Attempt to identify by temporary ID
36
		$member = null;
37
		$email = null;
38
		if(!empty($data['tempid'])) {
39
			// Find user by tempid, in case they are re-validating an existing session
40
			$member = Member::member_from_tempid($data['tempid']);
41
			if($member) $email = $member->Email;
42
		}
43
44
		// Otherwise, get email from posted value instead
45
		if(!$member && !empty($data['Email'])) {
46
			$email = $data['Email'];
47
		}
48
49
		// 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...
50
		$asDefaultAdmin = $email === Security::default_admin_username();
51
		if($asDefaultAdmin) {
52
			// If logging is as default admin, ensure record is setup correctly
53
			$member = Member::default_admin();
54
			$success = !$member->isLockedOut() && Security::check_default_admin($email, $data['Password']);
55
			//protect against failed login
56
			if($success) {
57
				return $member;
58
			}
59
		}
60
61
		// Attempt to identify user by email
62
		if(!$member && $email) {
63
			// Find user by email
64
			$member = Member::get()
65
				->filter(Member::config()->unique_identifier_field, $email)
66
				->first();
67
		}
68
69
		// Validate against member if possible
70
		if($member && !$asDefaultAdmin) {
71
			$result = $member->checkPassword($data['Password']);
72
			$success = $result->valid();
73
		} elseif (!$asDefaultAdmin) {
74
			// spoof a login attempt
75
			$member = Member::create();
76
			$member->Email = $email;
77
			$member->{Member::config()->unique_identifier_field} = $data['Password'] . '-wrong';
78
			$member->PasswordEncryption = 'none';
79
			$result = $member->checkPassword($data['Password']);
80
			$member = null;
81
		} else {
82
			$result = new ValidationResult(false, _t('Member.ERRORWRONGCRED'));
83
		}
84
85
		// Emit failure to member and form (if available)
86
		if(!$success) {
87
			if($member) $member->registerFailedLogin();
88
			if($form) $form->sessionMessage($result->message(), 'bad');
89
		} else {
90
			if($member) $member->registerSuccessfulLogin();
91
		}
92
93
		return $member;
94
	}
95
96
	/**
97
	 * Log login attempt
98
	 * TODO We could handle this with an extension
99
	 *
100
	 * @param array $data
101
	 * @param Member $member
102
	 * @param bool $success
103
	 */
104
	protected static function record_login_attempt($data, $member, $success) {
105
		if(!Security::config()->login_recording && !Member::config()->lock_out_after_incorrect_logins) return;
106
107
		// Check email is valid
108
		$email = isset($data['Email']) ? $data['Email'] : null;
109
		if(is_array($email)) {
110
			throw new InvalidArgumentException("Bad email passed to MemberAuthenticator::authenticate(): $email");
111
		}
112
113
		$attempt = new LoginAttempt();
114
		if($success) {
115
			// successful login (member is existing with matching password)
116
			$attempt->MemberID = $member->ID;
117
			$attempt->Status = 'Success';
118
119
			// Audit logging hook
120
			$member->extend('authenticated');
121
122
		} else {
123
			// Failed login - we're trying to see if a user exists with this email (disregarding wrong passwords)
124
			$attempt->Status = 'Failure';
125
			if($member) {
126
				// Audit logging hook
127
				$attempt->MemberID = $member->ID;
128
				$member->extend('authenticationFailed');
129
130
			} else {
131
				// Audit logging hook
132
				singleton('Member')->extend('authenticationFailedUnknownUser', $data);
133
			}
134
		}
135
136
		$attempt->Email = $email;
137
		$attempt->IP = Controller::curr()->getRequest()->getIP();
138
		$attempt->write();
139
	}
140
141
	/**
142
	 * Method to authenticate an user
143
	 *
144
	 * @param array $data Raw data to authenticate the user
145
	 * @param Form $form Optional: If passed, better error messages can be
146
	 *                             produced by using
147
	 *                             {@link Form::sessionMessage()}
148
	 * @return bool|Member Returns FALSE if authentication fails, otherwise
149
	 *                     the member object
150
	 * @see Security::setDefaultAdmin()
151
	 */
152
	public static function authenticate($data, Form $form = null) {
153
		// Find authenticated member
154
		$member = static::authenticate_member($data, $form, $success);
0 ignored issues
show
Bug introduced by
It seems like $form defined by parameter $form on line 152 can be null; however, MemberAuthenticator::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...
155
156
		// Optionally record every login attempt as a {@link LoginAttempt} object
157
		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 154 can be null; however, MemberAuthenticator::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...
158
159
		// Legacy migration to precision-safe password hashes.
160
		// A login-event with cleartext passwords is the only time
161
		// when we can rehash passwords to a different hashing algorithm,
162
		// bulk-migration doesn't work due to the nature of hashing.
163
		// See PasswordEncryptor_LegacyPHPHash class.
164
		if($success && $member && isset(self::$migrate_legacy_hashes[$member->PasswordEncryption])) {
165
			$member->Password = $data['Password'];
166
			$member->PasswordEncryption = self::$migrate_legacy_hashes[$member->PasswordEncryption];
167
			$member->write();
168
		}
169
170
		if($success) Session::clear('BackURL');
171
172
		return $success ? $member : null;
173
	}
174
175
176
	/**
177
	 * Method that creates the login form for this authentication method
178
	 *
179
	 * @param Controller The parent controller, necessary to create the
180
	 *                   appropriate form action tag
181
	 * @return Form Returns the login form to use with this authentication
182
	 *              method
183
	 */
184
	public static function get_login_form(Controller $controller) {
185
		return MemberLoginForm::create($controller, "LoginForm");
186
	}
187
188
	public static function get_cms_login_form(\Controller $controller) {
189
		return CMSMemberLoginForm::create($controller, "LoginForm");
190
	}
191
192
	public static function supports_cms() {
193
		// Don't automatically support subclasses of MemberAuthenticator
194
		return get_called_class() === __CLASS__;
195
	}
196
197
198
	/**
199
	 * Get the name of the authentication method
200
	 *
201
	 * @return string Returns the name of the authentication method.
202
	 */
203
	public static function get_name() {
204
		return _t('MemberAuthenticator.TITLE', "E-mail &amp; Password");
205
	}
206
}
207
208