Passed
Push — master ( 8d1327...021fdd )
by Magnar Ovedal
03:15
created

NoReuse::getPositions()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 2
nop 1
dl 0
loc 14
ccs 9
cts 9
cp 1
crap 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stadly\PasswordPolice\Rule;
6
7
use InvalidArgumentException;
8
use StableSort\StableSort;
9
use Stadly\PasswordPolice\Constraint\Position;
10
use Stadly\PasswordPolice\FormerPassword;
11
use Stadly\PasswordPolice\Password;
12
use Stadly\PasswordPolice\Policy;
13
use Stadly\PasswordPolice\HashFunction\HashFunctionInterface;
14
15
final class NoReuse implements RuleInterface
16
{
17
    /**
18
     * @var HashFunctionInterface Hash function.
19
     */
20
    private $hashFunction;
21
22
    /**
23
     * @var Position[] Rule constraints.
24
     */
25
    private $constraints;
26
27
    /**
28
     * @param HashFunctionInterface $hashFunction Hash function to use when comparing passwords.
29
     * @param int|null $count Number of former passwords to consider.
30
     * @param int $first First former password to consider.
31
     * @param int $weight Constraint weight.
32
     */
33 7
    public function __construct(
34
        HashFunctionInterface $hashFunction,
35
        ?int $count = null,
36
        int $first = 0,
37
        int $weight = 1
38
    ) {
39 7
        $this->hashFunction = $hashFunction;
40 7
        $this->addConstraint($count, $first, $weight);
41 4
    }
42
43
    /**
44
     * @param int|null $count Number of former passwords to consider.
45
     * @param int $first First former password to consider.
46
     * @param int $weight Constraint weight.
47
     * @return $this
48
     */
49 2
    public function addConstraint(?int $count = null, int $first = 0, int $weight = 1): self
50
    {
51 2
        $this->constraints[] = new Position($first, $count, $weight);
52
53
        StableSort::usort($this->constraints, function (Position $a, Position $b): int {
54 2
            return $b->getWeight() <=> $a->getWeight();
55 2
        });
56
57 2
        return $this;
58
    }
59
60
    /**
61
     * @return HashFunctionInterface Hash function.
62
     */
63 1
    public function getHashFunction(): HashFunctionInterface
64
    {
65 1
        return $this->hashFunction;
66
    }
67
68
    /**
69
     * Check whether a password is in compliance with the rule.
70
     *
71
     * @param Password|string $password Password to check.
72
     * @return bool Whether the password is in compliance with the rule.
73
     */
74 5
    public function test($password): bool
75
    {
76 5
        $positions = $this->getPositions($password);
77 5
        $constraint = $this->getViolation(...$positions);
78
79 5
        return $constraint === null;
80
    }
81
82
    /**
83
     * Enforce that a password is in compliance with the rule.
84
     *
85
     * @param Password|string $password Password that must adhere to the rule.
86
     * @throws RuleException If the password does not adhrere to the rule.
87
     */
88 3
    public function enforce($password): void
89
    {
90 3
        $positions = $this->getPositions($password);
91 3
        $constraint = $this->getViolation(...$positions);
92
93 3
        if ($constraint !== null) {
94 2
            throw new RuleException($this, $this->getMessage($constraint));
95
        }
96 1
    }
97
98
    /**
99
     * @param int... $positions Positions of former passwords matching the password.
0 ignored issues
show
Documentation Bug introduced by
The doc comment int... at position 0 could not be parsed: Unknown type name 'int...' at position 0 in int....
Loading history...
100
     * @return Position|null Constraint violated by the position.
101
     */
102 8
    private function getViolation(int... $positions): ?Position
103
    {
104 8
        foreach ($this->constraints as $constraint) {
105 8
            foreach ($positions as $position) {
106 7
                if ($constraint->test($position)) {
107 8
                    return $constraint;
108
                }
109
            }
110
        }
111
112 4
        return null;
113
    }
114
115
    /**
116
     * @param Password|string $password Password to compare with former passwords.
117
     * @return int[] Positions of former passwords matching the password.
118
     */
119 8
    private function getPositions($password): array
120
    {
121 8
        $positions = [];
122
123 8
        if ($password instanceof Password) {
124 7
            $position = 0;
125 7
            foreach ($password->getFormerPasswords() as $formerPassword) {
126 7
                if ($this->hashFunction->compare((string)$password, (string)$formerPassword)) {
127 7
                    $positions[] = $position;
128
                }
129 7
                ++$position;
130
            }
131
        }
132 8
        return $positions;
133
    }
134
135
    /**
136
     * @param Position $constraint Constraint that is violated.
137
     * @return string Message explaining the violation.
138
     */
139 2
    private function getMessage(Position $constraint): string
0 ignored issues
show
Unused Code introduced by
The parameter $constraint is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

139
    private function getMessage(/** @scrutinizer ignore-unused */ Position $constraint): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
140
    {
141 2
        $translator = Policy::getTranslator();
142
143 2
        return $translator->trans(
144 2
            'Cannot reuse former passwords.'
145
        );
146
    }
147
}
148