CodeManager   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 129
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 46
c 0
b 0
f 0
dl 0
loc 129
ccs 50
cts 50
cp 1
rs 10
wmc 14

5 Methods

Rating   Name   Duplication   Size   Complexity  
A generate() 0 23 3
A verify() 0 27 4
A generateOTP() 0 11 2
A __construct() 0 3 1
A checkCreationLimit() 0 19 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Prozorov\DataVerification;
6
7
use Prozorov\DataVerification\Exceptions\{LimitException, VerificationException};
8
use Prozorov\DataVerification\Models\Code;
9
use Prozorov\DataVerification\Types\Address;
10
use Prozorov\DataVerification\Events\OtpGenerationEvent;
11
12
class CodeManager
13
{
14
    /**
15
     * @var Configuration $config
16
     */
17
    protected $config;
18
19 12
    public function __construct(Configuration $config)
20
    {
21 12
        $this->config = $config;
22 12
    }
23
24
    /**
25
     * generate.
26
     *
27
     * @access	public
28
     * @param	Address	$address
29
     * @param	array  	$data    	Default: null
30
     * @param	string 	$fakeCode	Default: null
31
     * @return	Code
32
     */
33 7
    public function generate(Address $address, array $data = null): Code
34
    {
35 7
        $this->checkCreationLimit($address);
36
37 5
        $event = new OtpGenerationEvent('generating_one_time_password', $address);
38 5
        $this->config->emitEvent($event);
39
40 5
        $otp = $event->isModified() ? $event->getOtp() : $this->generateOTP();
41
42 5
        $verificationCode = md5((string) strtotime('now'));
43
44 5
        $code = new Code();
45 5
        $code->setVerificationCode($verificationCode)
46 5
            ->setOneTimePass($otp)
47 5
            ->setAddress($address);
48
49 5
        if (!empty($data)) {
50 2
            $code->setVerificationData($data);
51
        }
52
53 5
        $this->config->getCodeRepo()->save($code);
54
55 5
        return $code;
56
    }
57
58
    /**
59
     * verify.
60
     *
61
     * @access	public
62
     * @param	string	$verificationCode	
63
     * @param	string	$pass            	
64
     * @return	Code
65
     */
66 6
    public function verify(string $verificationCode, string $pass): Code
67
    {
68 6
        $seconds = $this->config->getPasswordValidationPeriod();
69
70 6
        $createdAfter = (new \Datetime())->sub(new \DateInterval('PT'.$seconds.'S'));
71 6
        $code = $this->config->getCodeRepo()->getOneUnvalidatedByCode($verificationCode, $createdAfter);
72
73 6
        if (!$code) {
74 1
            throw new \OutOfBoundsException('Данные не найдены');
75
        }
76
77 5
        if ($code->getOneTimePass() !== $pass) {
78 2
            $code->incrementAttempts();
79 2
            $this->config->getCodeRepo()->save($code);
80
81 2
            throw new VerificationException('Некорректно указан код');
82
        }
83
84 3
        if ($this->config->getAttempts() <= $code->getAttempts()) {
85 1
            throw new LimitException('Превышен лимит');
86
        }
87
88 2
        $code->setValidated();
89
        
90 2
        $this->config->getCodeRepo()->save($code);
91
92 2
        return $code;
93
    }
94
95
    /**
96
     * generateOTP.
97
     *
98
     * @access	protected
99
     * @param	string	$fakeCode	Default: null
100
     * @return	string
101
     */
102 4
    protected function generateOTP(): string
103
    {
104 4
        $symbols = $this->config->getAllowedSymbols();
105 4
        $length = $this->config->getPassLength();
106 4
        $otp = '';
107
108 4
        for ($i = 1; $i <= $length; $i++) {
109 4
            $otp .= $symbols[rand(0, (count($symbols) - 1))];
110
        }
111
112 4
        return $otp;
113
    }
114
115
    /**
116
     * checkCreationLimit.
117
     *
118
     * @access	protected
119
     * @param	Address	$address	
120
     * @return	void
121
     */
122 7
    protected function checkCreationLimit(Address $address): void
123
    {
124 7
        $threshold = $this->config->getCreationCodeThreshold();
125
126 7
        $createdAfter = (new \Datetime())->sub(new \DateInterval('PT'.$threshold.'S'));
127
128 7
        if ($this->config->getCodeRepo()->getLastCodeForAddress($address, $createdAfter)) {
129 1
            throw new LimitException('Превышен лимит');
130
        }
131
132 6
        $createdAfter = (new \Datetime())->sub(new \DateInterval('PT3600S'));
133
134 6
        $attempts = $this->config->getCodeRepo()->getCodesCountForAddress($address, $createdAfter);
135
136 6
        if ($attempts > 0) {
137 2
            $limitPerHour = $this->config->getLimitPerHour();
138
139 2
            if ($limitPerHour < $attempts) {
140 1
                throw new LimitException('Превышен лимит обращений в час');
141
            }
142
        }
143 5
    }
144
}
145