ExpressionEngineHasher   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 187
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 98.08%

Importance

Changes 0
Metric Value
wmc 24
lcom 1
cbo 0
dl 0
loc 187
ccs 51
cts 52
cp 0.9808
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
B make() 0 30 6
A guardAgainstMd5Collisions() 0 7 3
A generateSalt() 0 9 2
A hashUsingBcrypt() 0 13 3
B check() 0 18 5
A needsRehash() 0 11 3
A setRounds() 0 6 1
A getRounds() 0 4 1
1
<?php
2
3
namespace DrawMyAttention\ExpAuth;
4
5
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
6
use Exception;
7
8
class ExpressionEngineHasher implements HasherContract{
9
10
    private $hash_algorithms = array(
11
        128     => 'sha512',
12
        64      => 'sha256',
13
        40      => 'sha1',
14
        32      => 'md5'
15
    );
16
17
    /**
18
     * Default crypt cost factor.
19
     *
20
     * @var int
21
     */
22
    protected $rounds = 10;
23
24
    /**
25
     * The size of the hash created by the bcrypt algorithm.
26
     *
27
     * This is used to detect whether a user's password was created by Expression Engine (doesn't support
28
     * bcrypt, or from the newer system).
29
     *
30
     * @var int
31
     */
32
    protected $bcrypt_hash_size = 60;
33
34
    /**
35
     * Hash the given value.
36
     *
37
     * @param  string $value
38
     * @param  array $options
39
     * @return string
40
     * @throws Exception
41
     */
42 12
    public function make($value, array $options = array())
43
    {
44 12
        $this->guardAgainstMd5Collisions($value);
45
46
        // If no hash algorithm is explicitly specified, use bcrypt
47 11
        if (!isset($options['byte_size']) || $options['byte_size'] === false)
48 11
        {
49 4
            return $this->hashUsingBcrypt($value, $options);
50
        }
51 7
        elseif ( ! isset($this->hash_algorithms[$options['byte_size']]))
52
        {
53
            // The algorithm that was provided wasn't found in the array of available algorithms
54 1
            throw new Exception('No matching hash algorithm.');
55
        }
56
57
        // No salt? (not even blank), we'll regenerate
58 6
        if ($options['salt'] === false)
59 6
        {
60 1
            $options['salt'] = $this->generateSalt($options['byte_size']);
61 1
        }
62 5
        elseif (strlen($options['salt']) !== $options['byte_size'])
63
        {
64
            // A salt with an invalid length was provided. This can happen if
65
            // old code resets a new value, ignore it.
66 5
            $options['salt'] = '';
67 5
        }
68
69 6
        return hash($this->hash_algorithms[$options['byte_size']], $options['salt'].$value);
70
71
    }
72
73
    /**
74
     * Ensure that a hash doesn't exceed an operatble length.
75
     *
76
     * MD5 collisions usually happen above 1024 bits, so
77
     * we artificially limit their password to reasonable size.
78
     *
79
     * @access private
80
     * @param string $value
81
     * @throws Exception
82
     */
83 12
    private function guardAgainstMd5Collisions($value)
84
    {
85 12
        if (!$value || strlen($value) > 250)
86 12
        {
87 1
            throw new Exception("Hash length exceeds operable length.");
88
        }
89 11
    }
90
91
    /**
92
     * Generate a new SALT used for hashing the password.
93
     *
94
     * The salt should never be displayed, so any ascii character can be used for higher security.
95
     *
96
     * @param $byte_size
97
     * @return string
98
     */
99 1
    public function generateSalt($byte_size = 128)
100
    {
101 1
        $salt = '';
102 1
        for ($i = 0; $i < $byte_size; $i++)
103
        {
104 1
            $salt .= chr(mt_rand(33, 126));
105 1
        }
106 1
        return $salt;
107
    }
108
109
    /**
110
     * Hash a password using Bcrypt.
111
     *
112
     * @param string $value
113
     * @param array $options
114
     * @return string
115
     * @throws Exception
116
     */
117 4
    private function hashUsingBcrypt($value, $options)
118
    {
119 4
        $cost = isset($options['rounds']) ? $options['rounds'] : $this->rounds;
120
121 4
        $hash = password_hash($value, PASSWORD_BCRYPT, array('cost' => $cost));
122
123 4
        if ($hash === false)
124 4
        {
125
            throw new Exception("Bcrypt hashing not supported.");
126
        }
127
128 4
        return $hash;
129
    }
130
131
    /**
132
     * Check the given plain value against a hash.
133
     *
134
     * @param  string $value
135
     * @param  string $hashedValue
136
     * @param  array $options
137
     * @return bool
138
     */
139 7
    public function check($value, $hashedValue, array $options = array())
140
    {
141
        // If this is a default Laravel bcrypt hashed password,
142 7
        if(strlen($hashedValue) == $this->bcrypt_hash_size) {
143 3
            return password_verify($value, $hashedValue);
144
        }
145
146
        // As we are using the new ExpressionEngineUserProvider, the salt and byte_size should always be present. TODO revert to return false.
147 4
        if (!(isset($options['salt']) && isset($options['byte_size']))) {
148 4
            $options['byte_size'] = strlen($hashedValue);
149 4
            $options['salt'] = '';
150
            //return false;
151 4
        }
152
        // hash the incoming password & test against the original hash
153 4
        $hashed = $this->make($value, array('salt' => $options['salt'], 'byte_size' => $options['byte_size']));
154
155 4
        return ($hashed !== FALSE AND $hashedValue == $hashed);
156
    }
157
158
    /**
159
     * Check if the given hash has been hashed using the given options.
160
     *
161
     * @param  string $hashedValue
162
     * @param  array $options
163
     * @return bool
164
     */
165 3
    public function needsRehash($hashedValue, array $options = array())
166
    {
167 3
        if(strlen($hashedValue) == $this->bcrypt_hash_size){
168 2
            $cost = isset($options['rounds']) ? $options['rounds'] : $this->rounds;
169 2
            return password_needs_rehash($hashedValue, PASSWORD_BCRYPT, array('cost' => $cost));
170
        }
171
172
        //This is an Expression Engine user, so we have the opportunity to switch their password to using BCrypt
173 1
        return true;
174
175
    }
176
177
    /**
178
     * Set the default password work factor.
179
     *
180
     * @param  int  $rounds
181
     * @return $this
182
     */
183 1
    public function setRounds($rounds)
184
    {
185 1
        $this->rounds = (int) $rounds;
186
187 1
        return $this;
188
    }
189
190 1
    public function getRounds()
191
    {
192 1
        return $this->rounds;
193
    }
194
}
195