Passed
Push — 3.0 ( 8cdcee...90ba03 )
by Rubén
07:01
created

TemporaryMasterPassService::getMessageForEmail()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 1
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * sysPass
4
 *
5
 * @author    nuxsmin
6
 * @link      https://syspass.org
7
 * @copyright 2012-2018, Rubén Domínguez nuxsmin@$syspass.org
8
 *
9
 * This file is part of sysPass.
10
 *
11
 * sysPass is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by
13
 * the Free Software Foundation, either version 3 of the License, or
14
 * (at your option) any later version.
15
 *
16
 * sysPass is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 *  along with sysPass.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
namespace SP\Services\Crypt;
26
27
use SP\Core\AppInfoInterface;
28
use SP\Core\Crypt\Crypt;
29
use SP\Core\Crypt\Hash;
30
use SP\Core\Events\Event;
31
use SP\Core\Events\EventMessage;
32
use SP\Core\Messages\MailMessage;
33
use SP\DataModel\Dto\ConfigRequest;
34
use SP\Repositories\NoSuchItemException;
35
use SP\Services\Config\ConfigService;
36
use SP\Services\Mail\MailService;
37
use SP\Services\Service;
38
use SP\Services\ServiceException;
39
use SP\Services\User\UserService;
40
use SP\Util\PasswordUtil;
41
42
/**
43
 * Class TemporaryMasterPassService
44
 *
45
 * @package SP\Services\Crypt
46
 */
47
final class TemporaryMasterPassService extends Service
48
{
49
    /**
50
     * Número máximo de intentos
51
     */
52
    const MAX_ATTEMPTS = 50;
53
    /**
54
     * Parámetros de configuración
55
     */
56
    const PARAM_PASS = 'tempmaster_pass';
57
    const PARAM_KEY = 'tempmaster_passkey';
58
    const PARAM_HASH = 'tempmaster_passhash';
59
    const PARAM_TIME = 'tempmaster_passtime';
60
    const PARAM_MAX_TIME = 'tempmaster_maxtime';
61
    const PARAM_ATTEMPTS = 'tempmaster_attempts';
62
    /**
63
     * @var ConfigService
64
     */
65
    protected $configService;
66
    /**
67
     * @var int
68
     */
69
    protected $maxTime;
70
71
    /**
72
     * Crea una clave temporal para encriptar la clave maestra y guardarla.
73
     *
74
     * @param int $maxTime El tiempo máximo de validez de la clave
75
     *
76
     * @return string
77
     * @throws ServiceException
78
     */
79
    public function create($maxTime = 14400)
80
    {
81
        try {
82
            $this->maxTime = time() + $maxTime;
83
84
            // Encriptar la clave maestra con hash aleatorio generado
85
            $randomKey = PasswordUtil::generateRandomBytes(32);
86
            $secureKey = Crypt::makeSecuredKey($randomKey);
87
88
            $configRequest = new ConfigRequest();
89
            $configRequest->add(self::PARAM_PASS, Crypt::encrypt($this->getMasterKeyFromContext(), $secureKey, $randomKey));
90
            $configRequest->add(self::PARAM_KEY, $secureKey);
91
            $configRequest->add(self::PARAM_HASH, Hash::hashKey($randomKey));
92
            $configRequest->add(self::PARAM_TIME, time());
93
            $configRequest->add(self::PARAM_MAX_TIME, $this->maxTime);
94
            $configRequest->add(self::PARAM_ATTEMPTS, 0);
95
96
            $this->configService->saveBatch($configRequest);
97
98
            // Guardar la clave temporal hasta que finalice la sesión
99
            $this->context->setTemporaryMasterPass($randomKey);
100
101
            $this->eventDispatcher->notifyEvent('create.tempMasterPassword',
102
                new Event($this, EventMessage::factory()
103
                    ->addDescription(__u('Generate temporary password')))
104
            );
105
106
            return $randomKey;
107
        } catch (\Exception $e) {
108
            processException($e);
109
110
            throw new ServiceException(__u('Error while generating the temporary password'));
111
        }
112
    }
113
114
    /**
115
     * Comprueba si la clave temporal es válida
116
     *
117
     * @param string $pass clave a comprobar
118
     *
119
     * @return bool
120
     * @throws ServiceException
121
     */
122
    public function checkTempMasterPass($pass)
123
    {
124
        try {
125
            $isValid = false;
126
            $passMaxTime = (int)$this->configService->getByParam(self::PARAM_MAX_TIME);
127
128
            // Comprobar si el tiempo de validez o los intentos se han superado
129
            if ($passMaxTime === 0) {
130
                $this->eventDispatcher->notifyEvent('check.tempMasterPassword',
131
                    new Event($this, EventMessage::factory()->addDescription(__u('Temporary password expired')))
132
                );
133
134
                return $isValid;
135
            }
136
137
            $passTime = (int)$this->configService->getByParam(self::PARAM_TIME);
138
            $attempts = (int)$this->configService->getByParam(self::PARAM_ATTEMPTS);
139
140
            if ((!empty($passTime) && time() > $passMaxTime)
141
                || $attempts >= self::MAX_ATTEMPTS
142
            ) {
143
                $this->expire();
144
145
                return $isValid;
146
            }
147
148
            $isValid = Hash::checkHashKey($pass, $this->configService->getByParam(self::PARAM_HASH));
149
150
            if (!$isValid) {
151
                $this->configService->save(self::PARAM_ATTEMPTS, $attempts + 1);
152
            }
153
154
            return $isValid;
155
        } catch (NoSuchItemException $e) {
156
            return false;
157
        } catch (\Exception $e) {
158
            processException($e);
159
160
            throw new ServiceException(__u('Error while checking the temporary password'));
161
        }
162
    }
163
164
    /**
165
     * @throws ServiceException
166
     */
167
    protected function expire()
168
    {
169
        $configRequest = new ConfigRequest();
170
        $configRequest->add(self::PARAM_PASS, '');
171
        $configRequest->add(self::PARAM_KEY, '');
172
        $configRequest->add(self::PARAM_HASH, '');
173
        $configRequest->add(self::PARAM_TIME, 0);
174
        $configRequest->add(self::PARAM_MAX_TIME, 0);
175
        $configRequest->add(self::PARAM_ATTEMPTS, 0);
176
177
        $this->configService->saveBatch($configRequest);
178
179
        $this->eventDispatcher->notifyEvent('expire.tempMasterPassword',
180
            new Event($this, EventMessage::factory()
181
                ->addDescription(__u('Temporary password expired')))
182
        );
183
    }
184
185
    /**
186
     * @param $groupId
187
     * @param $key
188
     *
189
     * @throws ServiceException
190
     * @throws \SP\Core\Exceptions\ConstraintException
191
     * @throws \SP\Core\Exceptions\QueryException
192
     */
193
    public function sendByEmailForGroup($groupId, $key)
194
    {
195
        $mailMessage = $this->getMessageForEmail($key);
196
197
        $emails = array_map(function ($value) {
198
            return $value->email;
199
        }, $this->dic->get(UserService::class)->getUserEmailForGroup($groupId));
200
201
        $this->dic->get(MailService::class)
202
            ->sendBatch($mailMessage->getTitle(), $emails, $mailMessage);
203
    }
204
205
    /**
206
     * @param $key
207
     *
208
     * @throws ServiceException
209
     * @throws \SP\Core\Exceptions\ConstraintException
210
     * @throws \SP\Core\Exceptions\QueryException
211
     */
212
    public function sendByEmailForAllUsers($key)
213
    {
214
        $mailMessage = $this->getMessageForEmail($key);
215
216
        $emails = array_map(function ($value) {
217
            return $value->email;
218
        }, $this->dic->get(UserService::class)->getUserEmailForAll());
219
220
        $this->dic->get(MailService::class)
221
            ->sendBatch($mailMessage->getTitle(), $emails, $mailMessage);
222
    }
223
224
    /**
225
     * @param $key
226
     *
227
     * @return MailMessage
228
     */
229
    private function getMessageForEmail($key)
230
    {
231
        $mailMessage = new MailMessage();
232
        $mailMessage->setTitle(sprintf(__('%s Master Password'), AppInfoInterface::APP_NAME));
233
        $mailMessage->addDescription(__('A new sysPass master password has been generated, so next time you log into the application it will be requested.'));
234
        $mailMessage->addDescription(sprintf(__('The new Master Password is: %s'), $key));
235
        $mailMessage->addDescription(sprintf(__('This password will be valid until: %s'), date('r', $this->getMaxTime())));
236
        $mailMessage->addDescription(__('Please, don\'t forget to log in as soon as possible to save the changes.'));
237
238
        return $mailMessage;
239
    }
240
241
    /**
242
     * @return int
243
     */
244
    public function getMaxTime()
245
    {
246
        return $this->maxTime;
247
    }
248
249
    /**
250
     * Devuelve la clave maestra que ha sido encriptada con la clave temporal
251
     *
252
     * @param $key string con la clave utilizada para encriptar
253
     *
254
     * @return string con la clave maestra desencriptada
255
     * @throws NoSuchItemException
256
     * @throws ServiceException
257
     * @throws \Defuse\Crypto\Exception\CryptoException
258
     */
259
    public function getUsingKey($key)
260
    {
261
        return Crypt::decrypt($this->configService->getByParam(self::PARAM_PASS),
262
            $this->configService->getByParam(self::PARAM_KEY),
263
            $key);
264
    }
265
266
    /**
267
     * @throws \Psr\Container\ContainerExceptionInterface
268
     * @throws \Psr\Container\NotFoundExceptionInterface
269
     */
270
    protected function initialize()
271
    {
272
        $this->configService = $this->dic->get(ConfigService::class);
273
    }
274
}