Completed
Push — authenticator-refactor ( 082db8...732287 )
by Simon
06:42
created

LostPasswordHandler::lostpassword()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
c 0
b 0
f 0
cc 1
eloc 7
nc 1
nop 0
rs 9.4285
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\Session;
9
use SilverStripe\Control\RequestHandler;
10
use SilverStripe\Core\Injector\Injector;
11
use SilverStripe\Forms\Form;
12
use SilverStripe\ORM\ValidationResult;
13
use SilverStripe\Forms\FieldList;
14
use SilverStripe\Forms\EmailField;
15
use SilverStripe\Forms\FormAction;
16
use SilverStripe\Security\IdentityStore;
17
use SilverStripe\Security\Member;
18
use SilverStripe\Security\Security;
19
use SilverStripe\Core\Convert;
20
use SilverStripe\ORM\FieldType\DBField;
21
22
/**
23
 * Handle login requests from MemberLoginForm
24
 */
25
class LostPasswordHandler extends RequestHandler
26
{
27
    /**
28
     * Authentication class to use
29
     * @var string
30
     */
31
    protected $authenticatorClass = MemberAuthenticator::class;
32
33
    /**
34
     * @var array
35
     */
36
    private static $url_handlers = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
37
        'passwordsent/$EmailAddress' => 'passwordsent',
38
        ''                           => 'lostpassword',
39
    ];
40
41
    /**
42
     * Since the logout and dologin actions may be conditionally removed, it's necessary to ensure these
43
     * remain valid actions regardless of the member login state.
44
     *
45
     * @var array
46
     * @config
47
     */
48
    private static $allowed_actions = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
49
        'lostpassword',
50
        'LostPasswordForm',
51
        'passwordsent',
52
    ];
53
54
    private $link = null;
55
56
    /**
57
     * @param string $link The URL to recreate this request handler
58
     */
59
    public function __construct($link)
60
    {
61
        $this->link = $link;
62
        parent::__construct();
63
    }
64
65
    /**
66
     * Return a link to this request handler.
67
     * The link returned is supplied in the constructor
68
     *
69
     * @param string $action
70
     * @return string
71
     */
72
    public function link($action = null)
73
    {
74
        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...
75
            return Controller::join_links($this->link, $action);
76
        }
77
78
        return $this->link;
79
    }
80
81
    /**
82
     * URL handler for the initial lost-password screen
83
     *
84
     * @return array
85
     */
86
    public function lostpassword()
87
    {
88
89
        $message = _t(
90
            'SilverStripe\\Security\\Security.NOTERESETPASSWORD',
91
            'Enter your e-mail address and we will send you a link with which you can reset your password'
92
        );
93
94
        return [
95
            'Content' => DBField::create_field('HTMLFragment', "<p>$message</p>"),
96
            'Form'    => $this->lostPasswordForm(),
97
        ];
98
    }
99
100
    /**
101
     * Show the "password sent" page, after a user has requested
102
     * to reset their password.
103
     *
104
     * @return array
105
     */
106
    public function passwordsent()
107
    {
108
        $request = $this->getRequest();
109
        $email = Convert::raw2xml(rawurldecode($request->param('EmailAddress')) . '.' . $request->getExtension());
110
111
        $message = _t(
112
            'SilverStripe\\Security\\Security.PASSWORDSENTTEXT',
113
            "Thank you! A reset link has been sent to '{email}', provided an account exists for this email"
114
            . " address.",
115
            ['email' => Convert::raw2xml($email)]
116
        );
117
118
        return [
119
            'Title'   => _t(
120
                'SilverStripe\\Security\\Security.PASSWORDSENTHEADER',
121
                "Password reset link sent to '{email}'",
122
                array('email' => $email)
123
            ),
124
            'Content' => DBField::create_field('HTMLFragment', "<p>$message</p>"),
125
            'Email'   => $email
126
        ];
127
    }
128
129
130
    /**
131
     * Factory method for the lost password form
132
     *
133
     * @skipUpgrade
134
     * @return Form Returns the lost password form
135
     */
136
    public function lostPasswordForm()
137
    {
138
        return LostPasswordForm::create(
139
            $this,
140
            $this->authenticatorClass,
141
            'lostPasswordForm',
142
            null,
143
            null,
144
            false
145
        );
146
    }
147
148
    /**
149
     * Redirect to password recovery form
150
     *
151
     * @return HTTPResponse
152
     */
153
    public function redirectToLostPassword()
154
    {
155
        $lostPasswordLink = Security::singleton()->Link('lostpassword');
156
157
        return $this->redirect($this->addBackURLParam($lostPasswordLink));
158
    }
159
160
    /**
161
     * Forgot password form handler method.
162
     * Called when the user clicks on "I've lost my password".
163
     * Extensions can use the 'forgotPassword' method to veto executing
164
     * the logic, by returning FALSE. In this case, the user will be redirected back
165
     * to the form without further action. It is recommended to set a message
166
     * in the form detailing why the action was denied.
167
     *
168
     * @skipUpgrade
169
     * @param array $data Submitted data
170
     * @return HTTPResponse
171
     */
172
    public function forgotPassword($data)
173
    {
174
        // Ensure password is given
175
        if (empty($data['Email'])) {
176
            $this->form->sessionMessage(
177
                _t(
178
                    'SilverStripe\\Security\\Member.ENTEREMAIL',
179
                    'Please enter an email address to get a password reset link.'
180
                ),
181
                'bad'
182
            );
183
184
            return $this->redirectToLostPassword();
185
        }
186
187
        // Find existing member
188
        $field = Member::config()->get('unique_identifier_field');
189
        /** @var Member $member */
190
        $member = Member::get()->filter([$field => $data['Email']])->first();
191
192
        // Allow vetoing forgot password requests
193
        $results = $this->extend('forgotPassword', $member);
194
        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...
195
            return $this->redirectToLostPassword();
196
        }
197
198
        if ($member) {
199
            $token = $member->generateAutologinTokenAndStoreHash();
200
201
            $this->sendEmail($member, $token);
202
        }
203
204
        // Avoid information disclosure by displaying the same status,
205
        // regardless wether the email address actually exists
206
        $link = Controller::join_links(
207
            $this->link('passwordsent'),
208
            rawurlencode($data['Email']),
209
            '/'
210
        );
211
212
        return $this->redirect($this->addBackURLParam($link));
213
    }
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