Completed
Push — master ( 04748f...585c72 )
by Marco
07:11
created

src/SecureCookie.php (8 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php namespace Comodojo\Cookies;
2
3
use \Comodojo\Cookies\CookieInterface\CookieInterface;
4
use \Comodojo\Exception\CookieException;
5
use \Comodojo\Cookies\CookieBase;
6
use \Crypt_AES;
7
8
/**
9
 * AES-encrypted cookie
10
 * 
11
 * @package     Comodojo Spare Parts
12
 * @author      Marco Giovinazzi <[email protected]>
13
 * @license     MIT
14
 *
15
 * LICENSE:
16
 * 
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 * THE SOFTWARE.
24
 */
25
26
class SecureCookie extends CookieBase implements CookieInterface {
27
28
    /*
29
     * AES key
30
     *
31
     * @var int
32
     */
33
    private $key = null;
34
35
    /**
36
     * Secure cookie constructor
37
     *
38
     * Setup cookie name and key
39
     *
40
     * @param   string   $name
41
     *
42
     * @param   string   $key
43
     *
44
     * @throws \Comodojo\Exception\CookieException
45
     */
46 21 View Code Duplication
    public function __construct($name, $key) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
47
48 21
        if ( empty($key) OR !is_scalar($key) ) throw new CookieException("Invalid secret key");
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
49
50 21
        $this->key = $key;
51
52
        try {
53
            
54 21
            $this->setName($name);
55
56 7
        } catch (CookieException $ce) {
57
            
58
            throw $ce;
59
60
        }
61
62 21
        if ( defined("COMODOJO_COOKIE_MAX_SIZE") ) {
63
64
            $this->max_cookie_size = filter_var(COMODOJO_COOKIE_MAX_SIZE, FILTER_VALIDATE_INT, array(
65
                'options' => array(
66
                    'default' => 4000
67
                )
68
            ));
69
70
        }
71
72 21
    }
73
74
    /**
75
     * Set cookie content
76
     *
77
     * @param   mixed   $value      Cookie content
78
     * @param   bool    $serialize  If true (default) cookie will be serialized first
79
     *
80
     * @return  \Comodojo\Cookies\SecureCookie
81
     *
82
     * @throws  \Comodojo\Exception\CookieException
83
     */
84 12 View Code Duplication
    public function setValue($value, $serialize = true) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
85
86 12
        if ( !is_scalar($value) AND $serialize === false ) throw new CookieException("Cannot set non-scalar value without serialization");
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
87
88 12
        if ( $serialize === true ) $value = serialize($value);
89
90 12
        $cipher = new Crypt_AES(CRYPT_AES_MODE_ECB);
91
92 4
        $cipher->setKeyLength(256);
93
94 4
        $cipher->setKey(self::clientSpecificKey($this->key));
95
96
        // added base64 encoding to avoid problems with binary data
97
98 4
        $encrypted_value = $cipher->encrypt($value);
99
100 4
        $cookie_value = base64_encode($encrypted_value);
101
102 4
        if ( strlen($cookie_value) > $this->max_cookie_size ) throw new CookieException("Cookie size larger than 4KB");
103
104 3
        $this->value = $cookie_value;
105
106 3
        return $this;
107
108
    }
109
110
    /**
111
     * Get cookie content
112
     *
113
     * @param   bool    $unserialize    If true (default) cookie will be unserialized first
114
     *
115
     * @return  mixed
116
     */
117 3 View Code Duplication
    public function getValue($unserialize = true) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
118
119 3
        $cipher = new Crypt_AES(CRYPT_AES_MODE_ECB);
120
121 3
        $cipher->setKeyLength(256);
122
123 3
        $cipher->setKey(self::clientSpecificKey($this->key));
124
125
        // added base64 encoding to avoid problems with binary data
126
127 3
        $encoded_cookie = base64_decode($this->value);
128
129 3
        if ( $encoded_cookie === false ) throw new CookieException("Cookie data cannot be decoded");
130
131 3
        $cookie = $cipher->decrypt($encoded_cookie);
132
133 3
        if ( $cookie === false ) throw new CookieException("Cookie data cannot be dectypted");
134
135 3
        return ($unserialize === true) ? unserialize($cookie) : $cookie;
136
137
    }
138
139
    /**
140
     * Static method to create a cookie quickly
141
     *
142
     * @param   string   $name  The cookie name
143
     *
144
     * @param   string   $key
145
     * 
146
     * @param   array    $properties    Array of properties cookie should have
147
     *
148
     * @return  \Comodojo\Cookies\SecureCookie
149
     *
150
     * @throws  \Comodojo\Exception\CookieException
151
     */
152 3 View Code Duplication
    public static function create($name, $key, $properties = array(), $serialize = true) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
153
154
        try {
155
156 3
            $cookie = new SecureCookie($name, $key);
157
158 3
            self::cookieProperties($cookie, $properties, $serialize);
159
160 1
        } catch (CookieException $ce) {
161
            
162
            throw $ce;
163
164
        }
165
166 3
        return $cookie;
167
168
    }
169
170
    /**
171
     * Static method to get a cookie quickly
172
     *
173
     * @param   string   $name  The cookie name
174
     *
175
     * @param   string   $key
176
     *
177
     * @return  \Comodojo\Cookies\SecureCookie
178
     *
179
     * @throws  \Comodojo\Exception\CookieException
180
     */
181 3 View Code Duplication
    public static function retrieve($name, $key) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
182
183
        try {
184
185 3
            $cookie = new SecureCookie($name, $key);
186
187 3
            $return = $cookie->load();
188
189 3
        } catch (CookieException $ce) {
190
            
191 3
            throw $ce;
192
193
        }
194
195
        return $return;
196
197
    }
198
199
    /**
200
     * Create a client-specific key using provided key,
201
     * the client remote address and (in case) the value of
202
     * HTTP_X_FORWARDED_FOR header
203
     *
204
     * @param   string   $key
205
     *
206
     * @return  string
207
     */
208 4
    private static function clientSpecificKey($key) {
0 ignored issues
show
clientSpecificKey uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
209
210 4
        if ( isset($_SERVER['REMOTE_ADDR']) ) {
211
212
            $client_hash = md5($_SERVER['REMOTE_ADDR'].(isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : ''), true);
213
214
            $server_hash = md5($key, true);
215
216
            $cookie_key = $client_hash.$server_hash;
217
218
        } else {
219
220 4
            $cookie_key = hash('sha256', $key);
221
222
        }
223
224 4
        return $cookie_key;
225
226
    }
227
228
}
229