From fdeb06e54c9c3a356760f670f98202e70269d0b7 Mon Sep 17 00:00:00 2001 From: freezestart Date: Sat, 8 Sep 2012 21:13:56 +0800 Subject: [PATCH 01/22] fixed readme.md wrong symbol Signed-off-by: freezestart --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 76367066..56f19dc6 100644 --- a/readme.md +++ b/readme.md @@ -27,7 +27,7 @@ ### Hello World: Route::get('/', function() { - return "Hello World!": + return "Hello World!"; }); ``` @@ -64,4 +64,4 @@ ## Contributing to Laravel ## License -Laravel is open-sourced software licensed under the MIT License. \ No newline at end of file +Laravel is open-sourced software licensed under the MIT License. From 5f99c81035523e097a54fa6290de7b233c6691c3 Mon Sep 17 00:00:00 2001 From: JoostK Date: Sun, 9 Sep 2012 20:52:33 +0200 Subject: [PATCH 02/22] Fixed a problem whith `Eloquent::get_dirty` When you had a synched Eloquent model (e.g. without changed values) but one of those values is `null`, then that value would be considered as dirty. `Eloquent::changed` returns false, but the value is present in `Eloquent::get_dirty`. This fix makes sure that a `null` value in `$attributes` is only present in `get_dirty` when it wasn't at all *set* in `$original`. --- laravel/database/eloquent/model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laravel/database/eloquent/model.php b/laravel/database/eloquent/model.php index 306d471a..645937f5 100644 --- a/laravel/database/eloquent/model.php +++ b/laravel/database/eloquent/model.php @@ -518,7 +518,7 @@ public function get_dirty() foreach ($this->attributes as $key => $value) { - if ( ! isset($this->original[$key]) or $value !== $this->original[$key]) + if ( ! array_key_exists($key, $this->original) or $value != $this->original[$key]) { $dirty[$key] = $value; } From 147e0ec656e9aadc9683ed58a88f286224047d0c Mon Sep 17 00:00:00 2001 From: JoostK Date: Mon, 10 Sep 2012 00:51:02 +0200 Subject: [PATCH 03/22] Implementation of Eloquent tests Basic Eloquent unit tests. Lacks database operations, relationship testing and some magic method calls. --- laravel/tests/application/models/model.php | 15 ++ laravel/tests/cases/eloquent.test.php | 292 +++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 laravel/tests/application/models/model.php create mode 100644 laravel/tests/cases/eloquent.test.php diff --git a/laravel/tests/application/models/model.php b/laravel/tests/application/models/model.php new file mode 100644 index 00000000..cd2c82bd --- /dev/null +++ b/laravel/tests/application/models/model.php @@ -0,0 +1,15 @@ +set_attribute('setter', 'setter: '.$setter); + } + + public function get_getter() + { + return 'getter: '.$this->get_attribute('getter'); + } + +} \ No newline at end of file diff --git a/laravel/tests/cases/eloquent.test.php b/laravel/tests/cases/eloquent.test.php new file mode 100644 index 00000000..ac489f39 --- /dev/null +++ b/laravel/tests/cases/eloquent.test.php @@ -0,0 +1,292 @@ + 'Taylor', 'age' => 25, 'setter' => 'foo'); + + $model = new Model($array); + + $this->assertEquals('Taylor', $model->name); + $this->assertEquals(25, $model->age); + $this->assertEquals('setter: foo', $model->setter); + } + + /** + * Test the Model::fill method. + * + * @group laravel + */ + public function testAttributesAreSetByFillMethod() + { + $array = array('name' => 'Taylor', 'age' => 25, 'setter' => 'foo'); + + $model = new Model(); + $model->fill($array); + + $this->assertEquals('Taylor', $model->name); + $this->assertEquals(25, $model->age); + $this->assertEquals('setter: foo', $model->setter); + } + + /** + * Test the Model::fill_raw method. + * + * @group laravel + */ + public function testAttributesAreSetByFillRawMethod() + { + $array = array('name' => 'Taylor', 'age' => 25, 'setter' => 'foo'); + + $model = new Model(); + $model->fill_raw($array); + + $this->assertEquals($array, $model->attributes); + } + + /** + * Test the Model::fill method with accessible. + * + * @group laravel + */ + public function testAttributesAreSetByFillMethodWithAccessible() + { + Model::$accessible = array('name', 'age'); + + $array = array('name' => 'Taylor', 'age' => 25, 'foo' => 'bar'); + + $model = new Model(); + $model->fill($array); + + $this->assertEquals('Taylor', $model->name); + $this->assertEquals(25, $model->age); + $this->assertNull($model->foo); + + Model::$accessible = null; + } + + /** + * Test the Model::fill method with empty accessible array. + * + * @group laravel + */ + public function testAttributesAreSetByFillMethodWithEmptyAccessible() + { + Model::$accessible = array(); + + $array = array('name' => 'Taylor', 'age' => 25, 'foo' => 'bar'); + + $model = new Model(); + $model->fill($array); + + $this->assertEquals(array(), $model->attributes); + $this->assertNull($model->name); + $this->assertNull($model->age); + $this->assertNull($model->foo); + + Model::$accessible = null; + } + + /** + * Test the Model::fill_raw method with accessible. + * + * @group laravel + */ + public function testAttributesAreSetByFillRawMethodWithAccessible() + { + Model::$accessible = array('name', 'age'); + + $array = array('name' => 'taylor', 'age' => 25, 'setter' => 'foo'); + + $model = new Model(); + $model->fill_raw($array); + + $this->assertEquals($array, $model->attributes); + + Model::$accessible = null; + } + + /** + * Test the Model::__set method. + * + * @group laravel + */ + public function testAttributeMagicSetterMethodChangesAttribute() + { + Model::$accessible = array('setter'); + + $array = array('setter' => 'foo', 'getter' => 'bar'); + + $model = new Model($array); + $model->setter = 'bar'; + $model->getter = 'foo'; + + $this->assertEquals('setter: bar', $model->get_attribute('setter')); + $this->assertEquals('foo', $model->get_attribute('getter')); + + Model::$accessible = null; + } + + /** + * Test the Model::__get method. + * + * @group laravel + */ + public function testAttributeMagicGetterMethodReturnsAttribute() + { + $array = array('setter' => 'foo', 'getter' => 'bar'); + + $model = new Model($array); + + $this->assertEquals('setter: foo', $model->setter); + $this->assertEquals('getter: bar', $model->getter); + } + + /** + * Test the Model::set_* method. + * + * @group laravel + */ + public function testAttributeSetterMethodChangesAttribute() + { + Model::$accessible = array('setter'); + + $array = array('setter' => 'foo', 'getter' => 'bar'); + + $model = new Model($array); + $model->set_setter('bar'); + $model->set_getter('foo'); + + $this->assertEquals('setter: bar', $model->get_attribute('setter')); + $this->assertEquals('foo', $model->get_attribute('getter')); + + Model::$accessible = null; + } + + /** + * Test the Model::get_* method. + * + * @group laravel + */ + public function testAttributeGetterMethodReturnsAttribute() + { + $array = array('setter' => 'foo', 'getter' => 'bar'); + + $model = new Model($array); + + $this->assertEquals('setter: foo', $model->get_setter()); + $this->assertEquals('getter: bar', $model->get_getter()); + } + + /** + * Test determination of dirty/changed attributes. + * + * @group laravel + */ + public function testDeterminationOfChangedAttributes() + { + $array = array('name' => 'Taylor', 'age' => 25, 'foo' => null); + + $model = new Model($array, true); + $model->name = 'Otwell'; + $model->new = null; + + $this->assertTrue($model->changed('name')); + $this->assertFalse($model->changed('age')); + $this->assertFalse($model->changed('foo')); + $this->assertFalse($model->changed('new')); + $this->assertTrue($model->dirty()); + $this->assertEquals(array('name' => 'Otwell', 'new' => null), $model->get_dirty()); + + $model->sync(); + + $this->assertFalse($model->changed('name')); + $this->assertFalse($model->changed('age')); + $this->assertFalse($model->changed('foo')); + $this->assertFalse($model->changed('new')); + $this->assertFalse($model->dirty()); + $this->assertEquals(array(), $model->get_dirty()); + } + + /** + * Test the Model::purge method. + * + * @group laravel + */ + public function testAttributePurge() + { + $array = array('name' => 'Taylor', 'age' => 25); + + $model = new Model($array); + $model->name = 'Otwell'; + $model->age = 26; + + $model->purge('name'); + + $this->assertFalse($model->changed('name')); + $this->assertNull($model->name); + $this->assertTrue($model->changed('age')); + $this->assertEquals(26, $model->age); + $this->assertEquals(array('age' => 26), $model->get_dirty()); + } + + /** + * Test the Model::table method. + * + * @group laravel + */ + public function testTableMethodReturnsCorrectName() + { + $model = new Model(); + $this->assertEquals('models', $model->table()); + + Model::$table = 'table'; + $this->assertEquals('table', $model->table()); + + Model::$table = null; + $this->assertEquals('models', $model->table()); + } + + /** + * Test the Model::to_array method. + * + * @group laravel + */ + public function testConvertingToArray() + { + Model::$hidden = array('password', 'hidden'); + + $array = array('name' => 'Taylor', 'age' => 25, 'password' => 'laravel', 'null' => null); + + $model = new Model($array); + + $first = new Model(array('first' => 'foo', 'password' => 'hidden')); + $second = new Model(array('second' => 'bar', 'password' => 'hidden')); + $third = new Model(array('third' => 'baz', 'password' => 'hidden')); + + $model->relationships['one'] = new Model(array('foo' => 'bar', 'password' => 'hidden')); + $model->relationships['many'] = array($first, $second, $third); + $model->relationships['hidden'] = new Model(array('should' => 'visible')); + $model->relationships['null'] = null; + + $this->assertEquals(array( + 'name' => 'Taylor', 'age' => 25, 'null' => null, + 'one' => array('foo' => 'bar'), + 'many' => array( + array('first' => 'foo'), + array('second' => 'bar'), + array('third' => 'baz'), + ), + 'hidden' => array('should' => 'visible'), + 'null' => null, + ), $model->to_array()); + + } + +} \ No newline at end of file From 01ba355787c7f5df9d92ca1148faa21526abb6eb Mon Sep 17 00:00:00 2001 From: Bryan te Beek Date: Mon, 10 Sep 2012 21:11:20 +0200 Subject: [PATCH 04/22] Optimize Str-helper (see pull request #1180) Signed-off-by: Bryan te Beek --- laravel/str.php | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/laravel/str.php b/laravel/str.php index 23f2b731..68130042 100644 --- a/laravel/str.php +++ b/laravel/str.php @@ -9,17 +9,22 @@ class Str { */ public static $pluralizer; - /** - * Get the default string encoding for the application. - * - * This method is simply a short-cut to Config::get('application.encoding'). - * - * @return string - */ - public static function encoding() - { - return Config::get('application.encoding'); - } + /** + * Cache application encoding locally to save expensive calls to Config::get(). + * + * @var string + */ + public static $encoding = null; + + /** + * Get the appliction.encoding without needing to request it from Config::get() each time. + * + * @return string + */ + protected static function encoding() + { + return static::$encoding ?: static::$encoding = Config::get('application.encoding'); + } /** * Get the length of a string. From 1ee55a3b38ad2bcf4dea5d2dcc4f5371c84588c2 Mon Sep 17 00:00:00 2001 From: Duru Can Celasun Date: Fri, 14 Sep 2012 13:28:02 +0300 Subject: [PATCH 05/22] Fix a tiny typo s/encourage/encouraged --- laravel/documentation/controllers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laravel/documentation/controllers.md b/laravel/documentation/controllers.md index 0f8555f2..eb3e490a 100644 --- a/laravel/documentation/controllers.md +++ b/laravel/documentation/controllers.md @@ -17,7 +17,7 @@ ## The Basics Controllers are classes that are responsible for accepting user input and managing interactions between models, libraries, and views. Typically, they will ask a model for data, and then return a view that presents that data to the user. -The usage of controllers is the most common method of implementing application logic in modern web-development. However, Laravel also empowers developers to implement their application logic within routing declarations. This is explored in detail in the [routing document](/docs/routing). New users are encourage to start with controllers. There is nothing that route-based application logic can do that controllers can't. +The usage of controllers is the most common method of implementing application logic in modern web-development. However, Laravel also empowers developers to implement their application logic within routing declarations. This is explored in detail in the [routing document](/docs/routing). New users are encouraged to start with controllers. There is nothing that route-based application logic can do that controllers can't. Controller classes should be stored in **application/controllers** and should extend the Base\_Controller class. A Home\_Controller class is included with Laravel. From 03acf9ca504deb0bab5ff8ca978cd45937304688 Mon Sep 17 00:00:00 2001 From: Aaron Kuzemchak Date: Sat, 15 Sep 2012 18:57:29 -0400 Subject: [PATCH 06/22] Fixes language URI routing issue Signed-off-by: Aaron Kuzemchak --- laravel/laravel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laravel/laravel.php b/laravel/laravel.php index 74c387f9..20ec8057 100644 --- a/laravel/laravel.php +++ b/laravel/laravel.php @@ -137,7 +137,7 @@ foreach ($languages as $language) { - if (starts_with($uri, $language)) + if (preg_match("#^{$language}(/.*)?$#i", $uri)) { Config::set('application.language', $language); From 581fcc5c47946882e560dd08c43a5005491fffe4 Mon Sep 17 00:00:00 2001 From: Aaron Kuzemchak Date: Sat, 15 Sep 2012 20:48:18 -0400 Subject: [PATCH 07/22] Cleaner regex Signed-off-by: Aaron Kuzemchak --- laravel/laravel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laravel/laravel.php b/laravel/laravel.php index 20ec8057..3e382b89 100644 --- a/laravel/laravel.php +++ b/laravel/laravel.php @@ -137,7 +137,7 @@ foreach ($languages as $language) { - if (preg_match("#^{$language}(/.*)?$#i", $uri)) + if (preg_match("#^{$language}(?:$|/)#i", $uri)) { Config::set('application.language', $language); From 33b5f6377c5fdf83acb413f938c9644779d4095f Mon Sep 17 00:00:00 2001 From: Shawn McCool Date: Wed, 19 Sep 2012 18:13:31 +0300 Subject: [PATCH 08/22] Update laravel/documentation/routing.md Added a warning about using Controller::detect() for routing. --- laravel/documentation/routing.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/laravel/documentation/routing.md b/laravel/documentation/routing.md index eee1bb8c..38ad8075 100644 --- a/laravel/documentation/routing.md +++ b/laravel/documentation/routing.md @@ -295,6 +295,8 @@ #### Register all controllers for the application: If you wish to automatically detect the controllers in a bundle, just pass the bundle name to the method. If no bundle is specified, the application folder's controller directory will be searched. +> **Note:** It is important to note that this method gives you no control over the order in which controllers are loaded. Controller::detect() should only be used to Route controllers in very small sites. "Manually" routing controllers gives you much more control, is more self-documenting, and is certainly advised. + #### Register all controllers for the "admin" bundle: Route::controller(Controller::detect('admin')); From 3d51200a882d5a557dd14c88f1ae34f539b353b6 Mon Sep 17 00:00:00 2001 From: Joel Marcotte Date: Thu, 20 Sep 2012 17:54:28 -0400 Subject: [PATCH 09/22] Auth token now nulled on logout Signed-off-by: Joel Marcotte --- laravel/auth/drivers/driver.php | 2 ++ laravel/tests/cases/auth.test.php | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/laravel/auth/drivers/driver.php b/laravel/auth/drivers/driver.php index db649d24..96e90637 100644 --- a/laravel/auth/drivers/driver.php +++ b/laravel/auth/drivers/driver.php @@ -127,6 +127,8 @@ public function logout() $this->cookie($this->recaller(), null, -2000); Session::forget($this->token()); + + $this->token = null; } /** diff --git a/laravel/tests/cases/auth.test.php b/laravel/tests/cases/auth.test.php index 16ba428a..f038a176 100644 --- a/laravel/tests/cases/auth.test.php +++ b/laravel/tests/cases/auth.test.php @@ -294,9 +294,6 @@ public function testLogoutMethodLogsOutUser() Auth::logout(); - // A workaround since Cookie will is only stored in memory, until Response class is called. - Auth::driver()->token = null; - $this->assertNull(Auth::user()); $this->assertFalse(isset(Session::$instance->session['data']['laravel_auth_drivers_fluent_login'])); From 8c580d17fbb006be811a7979a7faf305ad7ca697 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 24 Sep 2012 01:01:14 +0930 Subject: [PATCH 10/22] You can only pass the ID of a user, not an object. Signed-off-by: Jason Lewis --- laravel/documentation/auth/usage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/laravel/documentation/auth/usage.md b/laravel/documentation/auth/usage.md index 8a9a0650..4e7ba5fe 100644 --- a/laravel/documentation/auth/usage.md +++ b/laravel/documentation/auth/usage.md @@ -50,9 +50,9 @@ ## Logging In return "You're logged in!"; } -Use the **login** method to login a user without checking their credentials, such as after a user first registers to use your application. Just pass your user object or the user's ID: +Use the **login** method to login a user without checking their credentials, such as after a user first registers to use your application. Just pass the user's ID: - Auth::login($user); + Auth::login($user->id); Auth::login(15); From 258169ea0000a8b90f5d56b7102c2212e0bad612 Mon Sep 17 00:00:00 2001 From: RK Date: Mon, 24 Sep 2012 09:58:20 -0400 Subject: [PATCH 11/22] [#1261] get_key now pulls from $original instead This is in reference to issue #1261, where Model->get_key() returns the key from the $attributes instead of from the $original property. This breaks the functionality of a model with a primary key that may change, as the SQL generated will be something like: UPDATE `model` SET `key` = 'new-key' WHERE `key` = 'new-key'; Which won't update the model in the database. --- laravel/database/eloquent/model.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/laravel/database/eloquent/model.php b/laravel/database/eloquent/model.php index c2566496..c57b36ad 100644 --- a/laravel/database/eloquent/model.php +++ b/laravel/database/eloquent/model.php @@ -544,7 +544,7 @@ public function get_dirty() */ public function get_key() { - return $this->get_attribute(static::$key); + return get_array($this->original, static::$key); } /** @@ -721,7 +721,7 @@ public function __isset($key) { if (array_key_exists($key, $this->$source)) return true; } - + if (method_exists($this, $key)) return true; } From f148f6211c378864c34c19f0aebcac97832aa9fe Mon Sep 17 00:00:00 2001 From: RK Date: Mon, 24 Sep 2012 10:35:07 -0400 Subject: [PATCH 12/22] Fixing the array_get misspelling. --- laravel/database/eloquent/model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laravel/database/eloquent/model.php b/laravel/database/eloquent/model.php index c57b36ad..b1ee5176 100644 --- a/laravel/database/eloquent/model.php +++ b/laravel/database/eloquent/model.php @@ -544,7 +544,7 @@ public function get_dirty() */ public function get_key() { - return get_array($this->original, static::$key); + return array_get($this->original, static::$key); } /** From 110966bd6a382da47c1cb554cd4b38b9bdcab672 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 25 Sep 2012 08:40:29 -0500 Subject: [PATCH 13/22] working on cookie fingerprinting. --- laravel/cookie.php | 42 ++++++++++++++++++++++++++-- laravel/tests/cases/auth.test.php | 2 +- laravel/tests/cases/cookie.test.php | 6 ++-- laravel/tests/cases/session.test.php | 2 +- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/laravel/cookie.php b/laravel/cookie.php index 1c3b5873..21c67bb7 100644 --- a/laravel/cookie.php +++ b/laravel/cookie.php @@ -44,9 +44,14 @@ public static function has($name) */ public static function get($name, $default = null) { - if (isset(static::$jar[$name])) return static::$jar[$name]['value']; + if (isset(static::$jar[$name])) return static::parse(static::$jar[$name]['value']); - return array_get(Request::foundation()->cookies->all(), $name, $default); + if ( ! is_null($value = Request::foundation()->cookies->get($name))) + { + return static::parse($value); + } + + return value($default); } /** @@ -75,6 +80,8 @@ public static function put($name, $value, $expiration = 0, $path = '/', $domain $expiration = time() + ($expiration * 60); } + $value = sha1($value.Config::get('application.key')).'+'.$value; + // If the secure option is set to true, yet the request is not over HTTPS // we'll throw an exception to let the developer know that they are // attempting to send a secure cookie over the insecure HTTP. @@ -120,4 +127,35 @@ public static function forget($name, $path = '/', $domain = null, $secure = fals return static::put($name, null, -2000, $path, $domain, $secure); } + /** + * Parse a hash fingerprinted cookie value. + * + * @param string $value + * @return string + */ + protected static function parse($value) + { + $segments = explode('+', $value); + + // First we will make sure the cookie actually has enough segments to even + // be valid as being set by the application. If it does not we will go + // ahead and throw exceptions now since there the cookie is invalid. + if ( ! (count($segments) >= 2)) + { + throw new \Exception("Cookie was not set by application."); + } + + $value = implode('+', array_slice($segments, 1)); + + // Now we will check if the SHA-1 hash present in the first segment matches + // the ShA-1 hash of the rest of the cookie value, since the hash should + // have been set when the cookie was first created by the application. + if ($segments[0] == sha1($value.Config::get('application.key'))) + { + return $value; + } + + throw new \Exception("Cookie has been modified by client."); + } + } diff --git a/laravel/tests/cases/auth.test.php b/laravel/tests/cases/auth.test.php index 16ba428a..44ff671e 100644 --- a/laravel/tests/cases/auth.test.php +++ b/laravel/tests/cases/auth.test.php @@ -269,7 +269,7 @@ public function testLoginStoresRememberCookieWhenNeeded() $this->assertTrue(isset(Cookie::$jar['laravel_auth_drivers_fluent_remember'])); - $cookie = Cookie::$jar['laravel_auth_drivers_fluent_remember']['value']; + $cookie = Cookie::get('laravel_auth_drivers_fluent_remember'); $cookie = explode('|', Crypter::decrypt($cookie)); $this->assertEquals(1, $cookie[0]); $this->assertEquals('foo', Cookie::$jar['laravel_auth_drivers_fluent_remember']['path']); diff --git a/laravel/tests/cases/cookie.test.php b/laravel/tests/cases/cookie.test.php index f62f7857..e17070b0 100644 --- a/laravel/tests/cases/cookie.test.php +++ b/laravel/tests/cases/cookie.test.php @@ -67,7 +67,7 @@ protected function restartRequest() */ public function testHasMethodIndicatesIfCookieInSet() { - Cookie::$jar['foo'] = array('value' => 'bar'); + Cookie::$jar['foo'] = array('value' => sha1('bar'.Config::get('application.key')).'+bar'); $this->assertTrue(Cookie::has('foo')); $this->assertFalse(Cookie::has('bar')); @@ -82,7 +82,7 @@ public function testHasMethodIndicatesIfCookieInSet() */ public function testGetMethodCanReturnValueOfCookies() { - Cookie::$jar['foo'] = array('value' => 'bar'); + Cookie::$jar['foo'] = array('value' => sha1('bar'.Config::get('application.key')).'+bar'); $this->assertEquals('bar', Cookie::get('foo')); Cookie::put('bar', 'baz'); @@ -97,7 +97,7 @@ public function testGetMethodCanReturnValueOfCookies() public function testForeverShouldUseATonOfMinutes() { Cookie::forever('foo', 'bar'); - $this->assertEquals('bar', Cookie::$jar['foo']['value']); + $this->assertEquals(sha1('bar'.Config::get('application.key')).'+bar', Cookie::$jar['foo']['value']); // Shouldn't be able to test this cause while we indicate -2000 seconds // cookie expiration store timestamp. diff --git a/laravel/tests/cases/session.test.php b/laravel/tests/cases/session.test.php index cf6946a7..b46b5ba0 100644 --- a/laravel/tests/cases/session.test.php +++ b/laravel/tests/cases/session.test.php @@ -372,7 +372,7 @@ public function testSaveMethodSetsCookieWithCorrectValues() $cookie = Cookie::$jar[Config::get('session.cookie')]; - $this->assertEquals('foo', $cookie['value']); + $this->assertEquals(sha1('foo'.Config::get('application.key')).'+foo', $cookie['value']); // Shouldn't be able to test this cause session.lifetime store number of minutes // while cookie expiration store timestamp when it going to expired. // $this->assertEquals(Config::get('session.lifetime'), $cookie['expiration']); From ad313198dfe046aea88b490db0253b25afef7e92 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 25 Sep 2012 08:40:29 -0500 Subject: [PATCH 14/22] working on cookie fingerprinting. --- laravel/cookie.php | 42 ++++++++++++++++++++++++++-- laravel/tests/cases/auth.test.php | 2 +- laravel/tests/cases/cookie.test.php | 6 ++-- laravel/tests/cases/session.test.php | 2 +- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/laravel/cookie.php b/laravel/cookie.php index 1c3b5873..21c67bb7 100644 --- a/laravel/cookie.php +++ b/laravel/cookie.php @@ -44,9 +44,14 @@ public static function has($name) */ public static function get($name, $default = null) { - if (isset(static::$jar[$name])) return static::$jar[$name]['value']; + if (isset(static::$jar[$name])) return static::parse(static::$jar[$name]['value']); - return array_get(Request::foundation()->cookies->all(), $name, $default); + if ( ! is_null($value = Request::foundation()->cookies->get($name))) + { + return static::parse($value); + } + + return value($default); } /** @@ -75,6 +80,8 @@ public static function put($name, $value, $expiration = 0, $path = '/', $domain $expiration = time() + ($expiration * 60); } + $value = sha1($value.Config::get('application.key')).'+'.$value; + // If the secure option is set to true, yet the request is not over HTTPS // we'll throw an exception to let the developer know that they are // attempting to send a secure cookie over the insecure HTTP. @@ -120,4 +127,35 @@ public static function forget($name, $path = '/', $domain = null, $secure = fals return static::put($name, null, -2000, $path, $domain, $secure); } + /** + * Parse a hash fingerprinted cookie value. + * + * @param string $value + * @return string + */ + protected static function parse($value) + { + $segments = explode('+', $value); + + // First we will make sure the cookie actually has enough segments to even + // be valid as being set by the application. If it does not we will go + // ahead and throw exceptions now since there the cookie is invalid. + if ( ! (count($segments) >= 2)) + { + throw new \Exception("Cookie was not set by application."); + } + + $value = implode('+', array_slice($segments, 1)); + + // Now we will check if the SHA-1 hash present in the first segment matches + // the ShA-1 hash of the rest of the cookie value, since the hash should + // have been set when the cookie was first created by the application. + if ($segments[0] == sha1($value.Config::get('application.key'))) + { + return $value; + } + + throw new \Exception("Cookie has been modified by client."); + } + } diff --git a/laravel/tests/cases/auth.test.php b/laravel/tests/cases/auth.test.php index 16ba428a..44ff671e 100644 --- a/laravel/tests/cases/auth.test.php +++ b/laravel/tests/cases/auth.test.php @@ -269,7 +269,7 @@ public function testLoginStoresRememberCookieWhenNeeded() $this->assertTrue(isset(Cookie::$jar['laravel_auth_drivers_fluent_remember'])); - $cookie = Cookie::$jar['laravel_auth_drivers_fluent_remember']['value']; + $cookie = Cookie::get('laravel_auth_drivers_fluent_remember'); $cookie = explode('|', Crypter::decrypt($cookie)); $this->assertEquals(1, $cookie[0]); $this->assertEquals('foo', Cookie::$jar['laravel_auth_drivers_fluent_remember']['path']); diff --git a/laravel/tests/cases/cookie.test.php b/laravel/tests/cases/cookie.test.php index f62f7857..e17070b0 100644 --- a/laravel/tests/cases/cookie.test.php +++ b/laravel/tests/cases/cookie.test.php @@ -67,7 +67,7 @@ protected function restartRequest() */ public function testHasMethodIndicatesIfCookieInSet() { - Cookie::$jar['foo'] = array('value' => 'bar'); + Cookie::$jar['foo'] = array('value' => sha1('bar'.Config::get('application.key')).'+bar'); $this->assertTrue(Cookie::has('foo')); $this->assertFalse(Cookie::has('bar')); @@ -82,7 +82,7 @@ public function testHasMethodIndicatesIfCookieInSet() */ public function testGetMethodCanReturnValueOfCookies() { - Cookie::$jar['foo'] = array('value' => 'bar'); + Cookie::$jar['foo'] = array('value' => sha1('bar'.Config::get('application.key')).'+bar'); $this->assertEquals('bar', Cookie::get('foo')); Cookie::put('bar', 'baz'); @@ -97,7 +97,7 @@ public function testGetMethodCanReturnValueOfCookies() public function testForeverShouldUseATonOfMinutes() { Cookie::forever('foo', 'bar'); - $this->assertEquals('bar', Cookie::$jar['foo']['value']); + $this->assertEquals(sha1('bar'.Config::get('application.key')).'+bar', Cookie::$jar['foo']['value']); // Shouldn't be able to test this cause while we indicate -2000 seconds // cookie expiration store timestamp. diff --git a/laravel/tests/cases/session.test.php b/laravel/tests/cases/session.test.php index cf6946a7..b46b5ba0 100644 --- a/laravel/tests/cases/session.test.php +++ b/laravel/tests/cases/session.test.php @@ -372,7 +372,7 @@ public function testSaveMethodSetsCookieWithCorrectValues() $cookie = Cookie::$jar[Config::get('session.cookie')]; - $this->assertEquals('foo', $cookie['value']); + $this->assertEquals(sha1('foo'.Config::get('application.key')).'+foo', $cookie['value']); // Shouldn't be able to test this cause session.lifetime store number of minutes // while cookie expiration store timestamp when it going to expired. // $this->assertEquals(Config::get('session.lifetime'), $cookie['expiration']); From 04f22f086d45aea006260711ea73381f5620934e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 25 Sep 2012 08:42:03 -0500 Subject: [PATCH 15/22] Update change log. --- laravel/documentation/changes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/laravel/documentation/changes.md b/laravel/documentation/changes.md index d9b0ff41..013eb948 100644 --- a/laravel/documentation/changes.md +++ b/laravel/documentation/changes.md @@ -45,6 +45,7 @@ ## Contents ## Laravel 3.2.8 - Fix double slash bug in URLs when using languages and no "index.php". +- Fix possible security issue in Auth "remember me" cookies. ### Upgrading From 3.2.7 From 4eac00a009cb9c8188be2948fe684ebb66c2550a Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 25 Sep 2012 16:43:58 -0400 Subject: [PATCH 16/22] Use hash_hmac on cookie hashes. --- laravel/cookie.php | 19 +++++++++++++++---- laravel/tests/cases/cookie.test.php | 6 +++--- laravel/tests/cases/session.test.php | 2 +- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/laravel/cookie.php b/laravel/cookie.php index 21c67bb7..56077ff7 100644 --- a/laravel/cookie.php +++ b/laravel/cookie.php @@ -80,7 +80,7 @@ public static function put($name, $value, $expiration = 0, $path = '/', $domain $expiration = time() + ($expiration * 60); } - $value = sha1($value.Config::get('application.key')).'+'.$value; + $value = static::hash($value).'+'.$value; // If the secure option is set to true, yet the request is not over HTTPS // we'll throw an exception to let the developer know that they are @@ -127,6 +127,17 @@ public static function forget($name, $path = '/', $domain = null, $secure = fals return static::put($name, null, -2000, $path, $domain, $secure); } + /** + * Hash the given cookie value. + * + * @param string $value + * @return string + */ + public static function hash($value) + { + return hash_hmac('sha1', $value, Config::get('application.key')); + } + /** * Parse a hash fingerprinted cookie value. * @@ -142,7 +153,7 @@ protected static function parse($value) // ahead and throw exceptions now since there the cookie is invalid. if ( ! (count($segments) >= 2)) { - throw new \Exception("Cookie was not set by application."); + return null; } $value = implode('+', array_slice($segments, 1)); @@ -150,12 +161,12 @@ protected static function parse($value) // Now we will check if the SHA-1 hash present in the first segment matches // the ShA-1 hash of the rest of the cookie value, since the hash should // have been set when the cookie was first created by the application. - if ($segments[0] == sha1($value.Config::get('application.key'))) + if ($segments[0] == static::hash($value)) { return $value; } - throw new \Exception("Cookie has been modified by client."); + return null; } } diff --git a/laravel/tests/cases/cookie.test.php b/laravel/tests/cases/cookie.test.php index e17070b0..37d63553 100644 --- a/laravel/tests/cases/cookie.test.php +++ b/laravel/tests/cases/cookie.test.php @@ -67,7 +67,7 @@ protected function restartRequest() */ public function testHasMethodIndicatesIfCookieInSet() { - Cookie::$jar['foo'] = array('value' => sha1('bar'.Config::get('application.key')).'+bar'); + Cookie::$jar['foo'] = array('value' => Cookie::hash('bar').'+bar'); $this->assertTrue(Cookie::has('foo')); $this->assertFalse(Cookie::has('bar')); @@ -82,7 +82,7 @@ public function testHasMethodIndicatesIfCookieInSet() */ public function testGetMethodCanReturnValueOfCookies() { - Cookie::$jar['foo'] = array('value' => sha1('bar'.Config::get('application.key')).'+bar'); + Cookie::$jar['foo'] = array('value' => Cookie::hash('bar').'+bar'); $this->assertEquals('bar', Cookie::get('foo')); Cookie::put('bar', 'baz'); @@ -97,7 +97,7 @@ public function testGetMethodCanReturnValueOfCookies() public function testForeverShouldUseATonOfMinutes() { Cookie::forever('foo', 'bar'); - $this->assertEquals(sha1('bar'.Config::get('application.key')).'+bar', Cookie::$jar['foo']['value']); + $this->assertEquals(Cookie::hash('bar').'+bar', Cookie::$jar['foo']['value']); // Shouldn't be able to test this cause while we indicate -2000 seconds // cookie expiration store timestamp. diff --git a/laravel/tests/cases/session.test.php b/laravel/tests/cases/session.test.php index b46b5ba0..f93ce9b1 100644 --- a/laravel/tests/cases/session.test.php +++ b/laravel/tests/cases/session.test.php @@ -372,7 +372,7 @@ public function testSaveMethodSetsCookieWithCorrectValues() $cookie = Cookie::$jar[Config::get('session.cookie')]; - $this->assertEquals(sha1('foo'.Config::get('application.key')).'+foo', $cookie['value']); + $this->assertEquals(Cookie::hash('foo').'+foo', $cookie['value']); // Shouldn't be able to test this cause session.lifetime store number of minutes // while cookie expiration store timestamp when it going to expired. // $this->assertEquals(Config::get('session.lifetime'), $cookie['expiration']); From d64d6c90922aed4e9b504316489bafb30aeb3d02 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 26 Sep 2012 10:11:24 -0400 Subject: [PATCH 17/22] Fix logging issue when using the laravel.log event. --- laravel/log.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/laravel/log.php b/laravel/log.php index 6de027df..f2b2b3f8 100644 --- a/laravel/log.php +++ b/laravel/log.php @@ -49,15 +49,9 @@ public static function write($type, $message) Event::fire('laravel.log', array($type, $message)); } - // If there aren't listeners on the log event, we'll just write to the - // log files using the default conventions, writing one log file per - // day so the files don't get too crowded. - else - { - $message = static::format($type, $message); + $message = static::format($type, $message); - File::append(path('storage').'logs/'.date('Y-m-d').'.log', $message); - } + File::append(path('storage').'logs/'.date('Y-m-d').'.log', $message); } /** From 14c6ff169232e6ccd5ceadecd88801dcc4ea7b36 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 26 Sep 2012 10:13:30 -0400 Subject: [PATCH 18/22] Increment version and change log. --- artisan | 2 +- laravel/documentation/changes.md | 12 ++++++++++++ public/index.php | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/artisan b/artisan index 2f3dbeb6..9200cb9a 100644 --- a/artisan +++ b/artisan @@ -4,7 +4,7 @@ * Laravel - A PHP Framework For Web Artisans * * @package Laravel - * @version 3.2.8 + * @version 3.2.9 * @author Taylor Otwell * @link http://laravel.com */ diff --git a/laravel/documentation/changes.md b/laravel/documentation/changes.md index 013eb948..a3db0872 100644 --- a/laravel/documentation/changes.md +++ b/laravel/documentation/changes.md @@ -2,6 +2,8 @@ # Laravel Change Log ## Contents +- [Laravel 3.2.9](#3.2.9) +- [Upgrading From 3.2.8](#upgrade-3.2.9) - [Laravel 3.2.8](#3.2.8) - [Upgrading From 3.2.7](#upgrade-3.2.8) - [Laravel 3.2.7](#3.2.7) @@ -41,6 +43,16 @@ ## Contents - [Laravel 3.1](#3.1) - [Upgrading From 3.0](#upgrade-3.1) + +## Laravel 3.2.9 + +- Always log exceptions even when there are "logger" event listeners. + + +### Upgrading From 3.2.8 + +- Replace the **laravel** folder. + ## Laravel 3.2.8 diff --git a/public/index.php b/public/index.php index 3b213a95..f58e72f6 100644 --- a/public/index.php +++ b/public/index.php @@ -3,7 +3,7 @@ * Laravel - A PHP Framework For Web Artisans * * @package Laravel - * @version 3.2.8 + * @version 3.2.9 * @author Taylor Otwell * @link http://laravel.com */ From a976c555e6d701aeb35b37ce8847e7badf429cc0 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 26 Sep 2012 10:43:34 -0400 Subject: [PATCH 19/22] Added "view.filter" event so we can hook in final filters. --- laravel/view.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/laravel/view.php b/laravel/view.php index d8275c6e..7807af2e 100644 --- a/laravel/view.php +++ b/laravel/view.php @@ -367,7 +367,17 @@ public function get() ob_get_clean(); throw $e; } - return ob_get_clean(); + $content = ob_get_clean(); + + // The view filter event gives us a last chance to modify the + // evaluated contents of the view and return them. This lets + // us do something like run the contents through Jade, etc. + if (Event::listeners('view.filter')) + { + return Event::first('view.filter', $content); + } + + return $content; } /** From 633c2bde836f5a0b3898e85a28683ada645b6522 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 26 Sep 2012 11:20:03 -0400 Subject: [PATCH 20/22] Pass the path to the filter event. --- laravel/view.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laravel/view.php b/laravel/view.php index 7807af2e..d2544d24 100644 --- a/laravel/view.php +++ b/laravel/view.php @@ -374,7 +374,7 @@ public function get() // us do something like run the contents through Jade, etc. if (Event::listeners('view.filter')) { - return Event::first('view.filter', $content); + return Event::first('view.filter', array($content, $this->path)); } return $content; From b043482905b4d53b393943e851b288f904db9840 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 26 Sep 2012 16:20:56 -0400 Subject: [PATCH 21/22] Improve view errors. --- laravel/error.php | 16 ++++++++++++++-- laravel/view.php | 9 +++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/laravel/error.php b/laravel/error.php index 6b75d210..a53d2e0b 100644 --- a/laravel/error.php +++ b/laravel/error.php @@ -15,6 +15,18 @@ public static function exception($exception, $trace = true) ob_get_level() and ob_end_clean(); + $message = $exception->getMessage(); + + // For Laravel view errors we want to show a prettier error: + $file = $exception->getFile(); + + if (str_contains($exception->getFile(), 'eval()') and str_contains($exception->getFile(), 'laravel/view.php')) + { + $message = 'Error rendering view: ['.View::$last['name'].']'.PHP_EOL.PHP_EOL.$message; + + $file = View::$last['path']; + } + // If detailed errors are enabled, we'll just format the exception into // a simple error message and display it on the screen. We don't use a // View in case the problem is in the View class. @@ -22,9 +34,9 @@ public static function exception($exception, $trace = true) { echo "

Unhandled Exception

Message:

-
".$exception->getMessage()."
+
".$message."

Location:

-
".$exception->getFile()." on line ".$exception->getLine()."
"; +
".$file." on line ".$exception->getLine()."
"; if ($trace) { diff --git a/laravel/view.php b/laravel/view.php index d2544d24..5ac37520 100644 --- a/laravel/view.php +++ b/laravel/view.php @@ -44,6 +44,13 @@ class View implements ArrayAccess { */ public static $cache = array(); + /** + * THe last view to be rendered. + * + * @var string + */ + public static $last; + /** * The Laravel view loader event name. * @@ -387,6 +394,8 @@ public function get() */ protected function load() { + static::$last = array('name' => $this->view, 'path' => $this->path); + if (isset(static::$cache[$this->path])) { return static::$cache[$this->path]; From 4de8e2d7179f8cdefb7458ac00ff55e305738a05 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 27 Sep 2012 09:05:29 -0400 Subject: [PATCH 22/22] Update change log. --- laravel/documentation/changes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/laravel/documentation/changes.md b/laravel/documentation/changes.md index a3db0872..417e4f85 100644 --- a/laravel/documentation/changes.md +++ b/laravel/documentation/changes.md @@ -47,6 +47,7 @@ ## Contents ## Laravel 3.2.9 - Always log exceptions even when there are "logger" event listeners. +- Fix nasty view exception messages. ### Upgrading From 3.2.8