Issues (219)

Branch: 4-cactus

Model/Action/ChangeCredentialsRequestAction.php (1 issue)

1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2017 ChannelWeb Srl, Chialab Srl
5
 *
6
 * This file is part of BEdita: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published
8
 * by the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
12
 */
13
14
namespace BEdita\Core\Model\Action;
15
16
use BEdita\Core\Exception\InvalidDataException;
17
use BEdita\Core\Model\Entity\AsyncJob;
18
use BEdita\Core\Model\Entity\User;
19
use BEdita\Core\Model\Validation\Validation;
20
use Cake\Database\Expression\QueryExpression;
21
use Cake\Event\Event;
22
use Cake\Event\EventDispatcherTrait;
23
use Cake\Event\EventListenerInterface;
24
use Cake\I18n\FrozenTime;
25
use Cake\Mailer\MailerAwareTrait;
26
use Cake\ORM\TableRegistry;
27
use Cake\Validation\Validator;
28
29
/**
30
 * Command to request an access credentials change
31
 * Typically a password change
32
 *
33
 * Input data MUST contain:
34
 *  - 'contact' user email, other contact methods in the future
35
 *  - 'change_url' base URL to use in email sent to user, actual change URL link will be
36
 *      {change_url}?uuid={uuid}
37
 *
38
 * @since 4.0.0
39
 */
40
class ChangeCredentialsRequestAction extends BaseAction implements EventListenerInterface
41
{
42
    use EventDispatcherTrait;
43
    use MailerAwareTrait;
44
45
    /**
46
     * The UsersTable table
47
     *
48
     * @var \BEdita\Core\Model\Table\UsersTable
49
     */
50
    protected $Users;
51
52
    /**
53
     * The AsyncJobs table
54
     *
55
     * @var \BEdita\Core\Model\Table\AsyncJobsTable
56
     */
57
    protected $AsyncJobs;
58
59
    /**
60
     * @inheritDoc
61
     */
62
    protected function initialize(array $config)
63
    {
64
        $this->Users = TableRegistry::getTableLocator()->get('Users');
65
        $this->AsyncJobs = TableRegistry::getTableLocator()->get('AsyncJobs');
66
67
        $this->getEventManager()->on($this);
68
    }
69
70
    /**
71
     * @inheritDoc
72
     */
73
    public function execute(array $data = [])
74
    {
75
        $errors = $this->validate($data);
76
        if ($errors !== true) {
77
            throw new InvalidDataException(__d('bedita', 'Invalid data'), $errors);
78
        }
79
80
        // operations are not in transaction: every failure stops following operations
81
        // and there's nothing to rollback
82
        $user = $this->getUser($data['contact']);
83
        $job = $this->createJob($user);
84
        $changeUrl = $this->getChangeUrl($job->uuid, $data['change_url']);
85
86
        $this->dispatchEvent('Auth.credentialsChangeRequest', [$user, $job, $changeUrl], $this->Users);
87
88
        return true;
89
    }
90
91
    /**
92
     * Validate input data.
93
     *
94
     * @param array $data Input
95
     * @return array|true Array of validation errors, or true if input is valid.
96
     */
97
    public function validate(array $data)
98
    {
99
        $validator = new Validator();
100
        $validator->setProvider('bedita', Validation::class);
101
102
        $validator->email('contact')
103
            ->notEmptyString('contact')
104
            ->requirePresence('contact')
105
106
            ->notEmptyString('change_url')
107
            ->requirePresence('change_url')
108
            ->add('activation_url', 'customUrl', [
109
                'rule' => 'url',
110
                'provider' => 'bedita',
111
            ]);
112
113
        $errors = $validator->validate($data);
114
        if (empty($errors)) {
115
            return true;
116
        }
117
118
        return $errors;
119
    }
120
121
    /**
122
     * Get user.
123
     *
124
     * @param string $contact Contact method (email).
125
     * @return \BEdita\Core\Model\Entity\User
126
     */
127
    protected function getUser($contact)
128
    {
129
        $user = $this->Users->find()
130
            ->where(function (QueryExpression $exp) use ($contact) {
131
                return $exp->eq($this->Users->aliasField('email'), $contact);
132
            })
133
            ->firstOrFail();
134
135
        return $user;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $user returns the type array which is incompatible with the documented return type BEdita\Core\Model\Entity\User.
Loading history...
136
    }
137
138
    /**
139
     * Create the credentials change async job
140
     *
141
     * @param \BEdita\Core\Model\Entity\User $user The user requesting change
142
     * @return \BEdita\Core\Model\Entity\AsyncJob
143
     */
144
    protected function createJob(User $user)
145
    {
146
        $asyncJobsTable = TableRegistry::getTableLocator()->get('AsyncJobs');
147
        $action = new SaveEntityAction(['table' => $asyncJobsTable]);
148
149
        return $action([
150
            'entity' => $asyncJobsTable->newEntity([]),
151
            'data' => [
152
                'service' => 'credentials_change',
153
                'payload' => [
154
                    'user_id' => $user->id,
155
                ],
156
                'scheduled_from' => new FrozenTime('1 day'),
157
                'priority' => 1,
158
            ],
159
        ]);
160
    }
161
162
    /**
163
     * Send change request email to user
164
     *
165
     * @param \Cake\Event\Event $event Dispatched event.
166
     * @param \BEdita\Core\Model\Entity\User $user The user.
167
     * @param \BEdita\Core\Model\Entity\AsyncJob $asyncJob Async job.
168
     * @param string $changeUrl Change URL
169
     * @return void
170
     */
171
    public function sendMail(Event $event, User $user, AsyncJob $asyncJob, $changeUrl)
172
    {
173
        $options = [
174
            'params' => compact('user', 'changeUrl'),
175
        ];
176
        $this->getMailer('BEdita/Core.User')->send('changeRequest', [$options]);
177
    }
178
179
    /**
180
     * Return the credentials change url
181
     *
182
     * @param string $uuid Change uuid
183
     * @param string $changeUrl Base change URL
184
     * @return string
185
     */
186
    protected function getChangeUrl($uuid, $changeUrl)
187
    {
188
        $changeUrl .= strpos($changeUrl, '?') === false ? '?' : '&';
189
190
        return sprintf('%suuid=%s', $changeUrl, $uuid);
191
    }
192
193
    /**
194
     * @inheritDoc
195
     */
196
    public function implementedEvents(): array
197
    {
198
        return [
199
            'Auth.credentialsChangeRequest' => 'sendMail',
200
        ];
201
    }
202
}
203