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

LostPasswordHandler::forgotPassword()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 42
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 21
nc 4
nop 2
dl 0
loc 42
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Security\MemberAuthenticator;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Control\Email\Email;
7
use SilverStripe\Control\HTTPResponse;
8
use SilverStripe\Control\RequestHandler;
9
use SilverStripe\Core\Convert;
10
use SilverStripe\Forms\Form;
11
use SilverStripe\ORM\FieldType\DBField;
12
use SilverStripe\Security\Member;
13
use SilverStripe\Security\Security;
14
15
/**
16
 * Handle login requests from MemberLoginForm
17
 */
18
class LostPasswordHandler extends RequestHandler
19
{
20
    /**
21
     * Authentication class to use
22
     * @var string
23
     */
24
    protected $authenticatorClass = MemberAuthenticator::class;
25
26
    /**
27
     * @var array
28
     */
29
    private static $url_handlers = [
30
        'passwordsent/$EmailAddress' => 'passwordsent',
31
        ''                           => 'lostpassword',
32
    ];
33
34
    /**
35
     * Since the logout and dologin actions may be conditionally removed, it's necessary to ensure these
36
     * remain valid actions regardless of the member login state.
37
     *
38
     * @var array
39
     * @config
40
     */
41
    private static $allowed_actions = [
42
        'lostpassword',
43
        'LostPasswordForm',
44
        'passwordsent',
45
    ];
46
47
    private $link = null;
48
49
    /**
50
     * @param string $link The URL to recreate this request handler
51
     */
52
    public function __construct($link)
53
    {
54
        $this->link = $link;
55
        parent::__construct();
56
    }
57
58
    /**
59
     * Return a link to this request handler.
60
     * The link returned is supplied in the constructor
61
     *
62
     * @param string $action
63
     * @return string
64
     */
65
    public function link($action = null)
66
    {
67
        if ($action) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $action of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
68
            return Controller::join_links($this->link, $action);
69
        }
70
71
        return $this->link;
72
    }
73
74
    /**
75
     * URL handler for the initial lost-password screen
76
     *
77
     * @return array
78
     */
79
    public function lostpassword()
80
    {
81
82
        $message = _t(
83
            'SilverStripe\\Security\\Security.NOTERESETPASSWORD',
84
            'Enter your e-mail address and we will send you a link with which you can reset your password'
85
        );
86
87
        return [
88
            'Content' => DBField::create_field('HTMLFragment', "<p>$message</p>"),
89
            'Form'    => $this->lostPasswordForm(),
90
        ];
91
    }
92
93
    /**
94
     * Show the "password sent" page, after a user has requested
95
     * to reset their password.
96
     *
97
     * @return array
98
     */
99
    public function passwordsent()
100
    {
101
        $request = $this->getRequest();
102
        $email = Convert::raw2xml(rawurldecode($request->param('EmailAddress')) . '.' . $request->getExtension());
103
104
        $message = _t(
105
            'SilverStripe\\Security\\Security.PASSWORDSENTTEXT',
106
            "Thank you! A reset link has been sent to '{email}', provided an account exists for this email"
107
            . " address.",
108
            ['email' => Convert::raw2xml($email)]
109
        );
110
111
        return [
112
            'Title'   => _t(
113
                'SilverStripe\\Security\\Security.PASSWORDSENTHEADER',
114
                "Password reset link sent to '{email}'",
115
                array('email' => $email)
116
            ),
117
            'Content' => DBField::create_field('HTMLFragment', "<p>$message</p>"),
118
            'Email'   => $email
119
        ];
120
    }
121
122
123
    /**
124
     * Factory method for the lost password form
125
     *
126
     * @skipUpgrade
127
     * @return Form Returns the lost password form
128
     */
129
    public function lostPasswordForm()
130
    {
131
        return LostPasswordForm::create(
132
            $this,
133
            $this->authenticatorClass,
134
            'lostPasswordForm',
135
            null,
136
            null,
137
            false
138
        );
139
    }
140
141
    /**
142
     * Redirect to password recovery form
143
     *
144
     * @return HTTPResponse
145
     */
146
    public function redirectToLostPassword()
147
    {
148
        $lostPasswordLink = Security::singleton()->Link('lostpassword');
149
150
        return $this->redirect($this->addBackURLParam($lostPasswordLink));
151
    }
152
153
    /**
154
     * Forgot password form handler method.
155
     * Called when the user clicks on "I've lost my password".
156
     * Extensions can use the 'forgotPassword' method to veto executing
157
     * the logic, by returning FALSE. In this case, the user will be redirected back
158
     * to the form without further action. It is recommended to set a message
159
     * in the form detailing why the action was denied.
160
     *
161
     * @skipUpgrade
162
     * @param array $data Submitted data
163
     * @param LostPasswordForm $form
164
     * @return HTTPResponse
165
     */
166
    public function forgotPassword($data, $form)
167
    {
168
        // Ensure password is given
169
        if (empty($data['Email'])) {
170
            $form->sessionMessage(
171
                _t(
172
                    'SilverStripe\\Security\\Member.ENTEREMAIL',
173
                    'Please enter an email address to get a password reset link.'
174
                ),
175
                'bad'
176
            );
177
178
            return $this->redirectToLostPassword();
179
        }
180
181
        // Find existing member
182
        $field = Member::config()->get('unique_identifier_field');
183
        /** @var Member $member */
184
        $member = Member::get()->filter([$field => $data['Email']])->first();
185
186
        // Allow vetoing forgot password requests
187
        $results = $this->extend('forgotPassword', $member);
188
        if ($results && is_array($results) && in_array(false, $results, true)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $results of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
189
            return $this->redirectToLostPassword();
190
        }
191
192
        if ($member) {
193
            $token = $member->generateAutologinTokenAndStoreHash();
194
195
            $this->sendEmail($member, $token);
196
        }
197
198
        // Avoid information disclosure by displaying the same status,
199
        // regardless wether the email address actually exists
200
        $link = Controller::join_links(
201
            $this->link('passwordsent'),
202
            rawurlencode($data['Email']),
203
            '/'
204
        );
205
206
        return $this->redirect($this->addBackURLParam($link));
207
    }
208
209
    /**
210
     * Send the email to the member that requested a reset link
211
     * @param Member $member
212
     * @param string $token
213
     * @return bool
214
     */
215
    protected function sendEmail($member, $token)
216
    {
217
        /** @var Email $email */
218
        $email = Email::create()
219
            ->setHTMLTemplate('SilverStripe\\Control\\Email\\ForgotPasswordEmail')
220
            ->setData($member)
221
            ->setSubject(_t(
222
                'SilverStripe\\Security\\Member.SUBJECTPASSWORDRESET',
223
                "Your password reset link",
224
                'Email subject'
225
            ))
226
            ->addData('PasswordResetLink', Security::getPasswordResetLink($member, $token))
227
            ->setTo($member->Email);
228
        return $email->send();
229
    }
230
}
231