diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..c1bdfd1 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,9 @@ +# .coveralls.yml example configuration + +# service name +service_name: travis-ci + +# for php-coveralls +src_dir: src +coverage_clover: build/logs/clover.xml +json_path: build/logs/coveralls-upload.json diff --git a/.gitignore b/.gitignore index fc7ea17..8b7ef35 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ /vendor composer.lock -.DS_Store diff --git a/.travis.yml b/.travis.yml index b51d696..44fc78a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,22 @@ language: php -php: - - 5.3 +php: - 5.4 + - 5.5 + - 5.6 + - hhvm + +matrix: + allow_failures: + - php: 5.6 before_script: - - curl -s http://getcomposer.org/installer | php - - php composer.phar install --dev + - travis_retry composer self-update + - travis_retry composer install --no-interaction --prefer-source --dev + +script: + - mkdir -p build/logs + - vendor/bin/phpunit -c phpunit.xml.dist --verbose -script: phpunit +after_script: + - php vendor/bin/coveralls -v diff --git a/README.md b/README.md index cc6c579..116e4e7 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,18 @@ -# Confide (Laravel4 Package) +# Confide _(A Laravel4 Package)_ ![Confide Poster](https://dl.dropbox.com/u/12506137/libs_bundles/confide.png) [![Build Status](https://api.travis-ci.org/Zizaco/confide.png)](https://travis-ci.org/Zizaco/confide) +[![Coverage Status](https://coveralls.io/repos/Zizaco/confide/badge.png?branch=master)](https://coveralls.io/r/Zizaco/confide?branch=master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Zizaco/confide/badges/quality-score.png)](https://scrutinizer-ci.com/g/Zizaco/confide/) [![ProjectStatus](http://stillmaintained.com/Zizaco/confide.png)](http://stillmaintained.com/Zizaco/confide) +[![Latest Stable Version](https://poser.pugx.org/zizaco/confide/v/stable.png)](https://packagist.org/packages/zizaco/confide) +[![Total Downloads](https://poser.pugx.org/zizaco/confide/downloads.png)](https://packagist.org/packages/zizaco/confide) +[![License](https://poser.pugx.org/zizaco/confide/license.png)](http://opensource.org/licenses/MIT) -Confide is a authentication solution for **Laravel4** made to eliminate repetitive tasks involving the management of users: Account creation, login, logout, confirmation by e-mail, password reset, etc. +[![SensioLabsInsight](https://insight.sensiolabs.com/projects/ec420846-0af4-4df4-b424-be90d9c3f98e/small.png)](https://insight.sensiolabs.com/projects/ec420846-0af4-4df4-b424-be90d9c3f98e) + +Confide is an authentication solution for **Laravel4** made to eliminate repetitive tasks involving the management of users: Account creation, login, logout, confirmation by e-mail, password reset, etc. Confide aims to be simple to use, quick to configure and flexible. @@ -17,11 +24,11 @@ Confide aims to be simple to use, quick to configure and flexible. - Account confirmation (through confirmation link). - Password reset (sending email with a change password link). - Easily render forms for login, signup and password reset. -- Generate customizable routes for login, signup, password reset, confirmation, etc. +- Generate routes for login, signup, password reset, confirmation, etc. - Generate a customizable controller that handles the basic user account actions. -- Contains a set of methods to help basic user features. -- Integrated with the Laravel Auth component/configs. -- Field/model validation (Powered by [Ardent](http://laravelbook.github.com/ardent "Ardent")). +- Contains a set of methods to help with basic user features. +- Integrated with the Laravel _Auth_ and _Reminders_ component/configs. +- User validation. - Login throttling. - Redirecting to previous route after authentication. - Checks for unique email and username in signup @@ -30,15 +37,10 @@ If you are looking for user roles and permissions see [Entrust](https://github.c For MongoDB support see [Confide Mongo](https://github.com/Zizaco/confide-mongo) -**Planned:** -- Captcha in user signup and password reset. - -**Warning:** - -By default a confirmation email is sent and users are required to confirm the email address. +**Warning:** _By default a confirmation email is sent and users are required to confirm the email address. It is easy to change this in the confide config file. -Change signup_email and signup_confirm to false if you do not want to send them an email and they do not need -to be confirmed to be able to login to the website. +Change `signup_email` and `signup_confirm` to false if you do not want to send them an email and they do not need +to be confirmed to be able to login to the website._ ## Quick start @@ -46,31 +48,31 @@ to be confirmed to be able to login to the website. In the `require` key of `composer.json` file add the following - "zizaco/confide": "3.2.x" + "zizaco/confide": "~4.0" Run the Composer update comand $ composer update -In your `config/app.php` add `'Zizaco\Confide\ConfideServiceProvider'` to the end of the `$providers` array +In your `config/app.php` add `'Zizaco\Confide\ServiceProvider'` to the end of the `providers` array 'providers' => array( 'Illuminate\Foundation\Providers\ArtisanServiceProvider', 'Illuminate\Auth\AuthServiceProvider', ... - 'Zizaco\Confide\ConfideServiceProvider', + 'Zizaco\Confide\ServiceProvider', ), -At the end of `config/app.php` add `'Confide' => 'Zizaco\Confide\ConfideFacade'` to the `$aliases` array +At the end of `config/app.php` add `'Confide' => 'Zizaco\Confide\Facade'` to the `aliases` array 'aliases' => array( 'App' => 'Illuminate\Support\Facades\App', 'Artisan' => 'Illuminate\Support\Facades\Artisan', ... - 'Confide' => 'Zizaco\Confide\ConfideFacade', + 'Confide' => 'Zizaco\Confide\Facade', ), @@ -80,6 +82,7 @@ Set the properly values to the `config/auth.php`. This values will be used by co Set the `address` and `name` from the `from` array in `config/mail.php`. Those will be used to send account confirmation and password reset emails to the users. + ### User model Now generate the Confide migration and the reminder password table migration: @@ -90,23 +93,24 @@ It will generate the `_confide_setup_users_table.php` migration. You $ php artisan migrate -It will setup a table containing `email`, `password`, `confirmation_code` and `confirmed` fields, which are the default fields needed for Confide use. Feel free to add more fields to the database. +It will setup a table containing `email`, `password`, `remember_token`, `confirmation_code` and `confirmed` columns, which are the default fields needed for Confide use. Feel free to add more columns to the table later. Change your User model in `app/models/User.php` to: 'Zizaco\Confide\Facade'` entry in `config/app.php` `'providers'` and `'aliases'` respectively. +2. User model (with the same name as in `config/auth.php`) should implement `Zizaco\Confide\ConfideUserInterface` interface. This will cause to methods like `forgotPassword()` and `confirm()` to be available. **Optional steps:** -1. Use `Confide` facade to dump login and signup forms easly with `makeLoginForm()` and `makeSignupForm()`. You can render the forms within your views by doing `{{ Confide::makeLoginForm()->render() }}`. -2. Generate a controller with the template contained in Confide throught the artisan command `$ php artisan confide:controller`. If a controller with the same name exists it will **NOT** be overwritten. -3. Generate routes matching the controller template throught the artisan command `$ php artisan confide:routes`. Your `routes.php` will **NOT** be overwritten. +1. Optionally you can use the trait `Zizaco\Confide\ConfideUser` in your user model. This will save a lot of time and will use "confide's default" implementation for the user. If you wish more customization you can write your own code. +2. Use `Confide` facade to dump login and signup forms easly with `makeLoginForm()` and `makeSignupForm()`. You can render the forms within your views by doing `{{ Confide::makeLoginForm()->render() }}`. +3. Generate a **controller** and a **repository** with the template contained in Confide throught the artisan command `$ php artisan confide:controller`. If a controller with the same name exists it will **NOT** be overwritten. +4. Generate routes matching the controller template throught the artisan command `$ php artisan confide:routes`. Don't worry, your `routes.php` will **NOT** be overwritten. ### Advanced -#### Using custom table / model name +#### The `UserRepository` class -You can change the model name that will be authenticated in the `config/auth.php` file. +You may have noticed that when generating the controller a `UserRepository` class has also been created. This class contains some code that doesn't belong to the "controller" purpose and will make your users controller a cleaner and more testable class. If you still have no idea why that class exists I recommend you to google _"Creating flexible Controllers in Laravel 4 using Repositories"_. _(wink)_ + +#### Using custom class, table or model name + +You can change the model name that will be considered the user in the `config/auth.php` file. Confide uses the values present in that configuration file. To change the controller name when dumping the default controller template you can use the --name option. - $ php artisan confide:controller --name Employee + $ php artisan confide:controller --name=Employee Will result in `EmployeeController` Then, when dumping the routes, you should use the --controller option to match the existing controller. - $ php artisan confide:routes --controller Employee + $ php artisan confide:routes --controller=Employee + +You can also generate controllers with namespace + + $ php artisan confide:controller --name=MyProject\\Auth\\User + +**Warning:** In bash, you will need to use double '\\\\' backslashes. This will result in `MyProject\Auth\UserController`. Also the generated file will be inside a directory equivalent to the namespace. _(wink)_ #### Using custom form or emails @@ -162,86 +177,44 @@ First, publish the config files: Then edit the view names in `app/config/packages/zizaco/confide/config.php`. -#### Update a User +#### Custom user validation -To update an user already in the database you'll Need to make sure your ruleset is using the unique validator within the User model. +You can implement your own validator by creating a class that implements the `UserValidatorInterface` and registering that class as *"confide.user_validator"*. - 'unique:users,username', - 'email' => 'email' - ); - - ?> - - email = Input::get('email'); - - // Save - // This was previously update, but Ardent changed. - $user->updateUniques(); - + // app/models/MyOwnValidator.php + class MyOwnValidator implements UserValidatorInterface { + + public function validate(ConfideUserInterface $user) + { + unset($user->password_confirmation); + return true; // If the user valid } } - - ?> - -This will allow you to update the current user. -#### Validate model fields +Then register it in IoC container as *"confide.user_validator"* -To change the validation rules of the User model you can take a look at [Ardent](http://laravelbook.github.com/ardent/#validation "Ardent Validation Rulez"). For example: + // app/start/global.php + ... + App::bind('confide.user_validator', 'MyOwnValidator'); - 'required|email', - 'password' => 'required|between:4,11|confirmed', - ); - - } - -Feel free to add more fields to your table and to the validation array. Then you should build your own sign-up form with the additional fields. - -NOTE: If you add fields to your validation rules into your model like above, do not forget you have to add those fields to the UserController store function also. If you forget this, the form will always return with an error. -Example: $user->terms = Input::get('terms'); - -#### Passing additional information to the make methods - -If you want to pass additional parameters to the forms, you can use an alternate syntax to achieve this. +If you want to pass additional parameters to the forms being rendered, you can use an alternate syntax to achieve this. Instead of using the make method: - + Confide::makeResetPasswordForm( $token ): You would use: View::make(Config::get('confide::reset_password_form')) ->with('token', $token); - + It produces the same output, but you would be able to add more inputs using 'with' just like any other view. #### RESTful controller @@ -255,71 +228,94 @@ Will result in a [RESTful controller](https://github.com/laravel/docs/blob/maste Then, when dumping the routes, you should use the --restful option to match the existing controller. $ php artisan confide:routes --restful - + #### User roles and permissions -In order not to bloat Confide with not related features, the role and permission was developed as another package: [Entrust](https://github.com/Zizaco/entrust). This package couples very well with Confide. +In order not to bloat Confide with not related features, the role and permission was developed as another package: [Entrust](https://github.com/Zizaco/entrust). Enstrust couples very well with Confide. See [Entrust](https://github.com/Zizaco/entrust) #### Redirecting to previous route after login -When defining your filter you should use the Redirect::guest('user/login') within your auth filter. For example: +When defining your filter you should use the Redirect::guest('users/login') within your auth filter. For example: // filters.php - Route::filter('auth', function() - { - if ( Auth::guest() ) // If the user is not logged in - { - return Redirect::guest('user/login'); + Route::filter('auth', function() { + // If the user is not logged in + if (Auth::guest()) { + return Redirect::guest('users/login'); } }); // Only authenticated users will be able to access routes that begins with // 'admin'. Ex: 'admin/posts', 'admin/categories'. - Route::when('admin*', 'auth'); + Route::when('admin*', 'auth'); or, if you are using [Entrust](https://github.com/Zizaco/entrust) ;) // filters.php Entrust::routeNeedsRole( 'admin*', 'Admin', function(){ - return Redirect::guest('user/login'); + return Redirect::guest('users/login'); } ); -Finally, it'll auto redirect if your controller's user/login function uses Redirect:intended('a/default/url/here') after a successful login. +Finally, it'll auto redirect if your controller's users/login function uses Redirect:intended('a/default/url/here') after a successful login. The [generated controller](https://github.com/Zizaco/confide/blob/master/src/views/generators/controller.blade.php) does exactly this. - -#### Validating a route - -If you want to validate whether a route exists, the `Confide::checkAction` function is what you are looking for. - -Currently it is used within the views to determine Non-RESTful vs RESTful routes. ## Troubleshooting -__[Exception] SQLSTATE[HY000]: General error: 1364 Field 'confirmation_code' doesn't have a default value...__ +__[2014-07-18 01:13:15] production.ERROR: exception 'Illuminate\Database\QueryException' with message 'SQLSTATE[42S22]: Column not found: 1054 Unknown column 'password_confirmation' in 'field list' (SQL: insert into \`users\` ...__ -If you overwrite the `beforeSave()` method in your model, make sure to call `parent::beforeSave()`: +The `password_confirmation` attribute should be removed from the object before being sent to the database. Make sure your user model implement the `ConfideUserInterface` and that it use the `ConfideUser` trait [as described above](#user-model). Otherwise if you are using a custom validator, you will have to unset `password_confirmation` before saving the user. - public function beforeSave( $forced = false ){ +__I receive a "Your account may not be confirmed" when trying to login__ - parent::beforeSave( $forced) // Don't forget this +You need to confirm a newly created user _(by "reaching" its `confirm()` method)_, otherwise you can disable the confirmation as a requirement to login in in the config file _(see bellow)_. You can easly confirm an user manually using Laravel's `artisan tinker` tool. - // Your stuff - } +__I'm not able to generate a controller with namespaces__ -__Confirmation link is not sent when user signup__ +In bash, you will need to use double '\\\\' backslashes. Also the generated file will be inside a directory equivalent to the namespace: -Same as above. If you overwrite the `afterSave()` method in your model, make sure to call `parent::afterSave()`: + $ php artisan confide:controller --name=MyProject\\Auth\\User __Users are able to login without confirming account__ If you want only confirmed users to login, in your `UserController`, instead of simply calling `logAttempt( $input )`, call `logAttempt( $input, true )`. The second parameter stands for _"confirmed_only"_. +__My application is crashing since I ran composer update__ + +*Confide 4.0.0* was a huge update where all the codebase has been rewritten. Some classes changed, the generators has been improved in order to match some better practices (like repositories and separated validator classes). See the _Release Notes_ bellow. + +If you have a legacy project that uses an older version of Confide, don't worry. You will be always able to specify a previous version in your `composer.json` file. + +For example: `"zizaco/confide": "~3.2"` will avoid composer download version 4.0 but will be able to download bugfixes of version 3.2. + ## Release Notes +### Version 4.0.0 Beta 3 +* Now you can customize how long will take for a password reset request to expire (default to 7 hours). +* Reordered validations +* Now all validations are called even if one of them fails. So all validation messages are sent at once. +* `validateIsUnique` method now sends key to attachErrorMsg and also check for errors on each `$identity` field at once + +### Version 4.0.0 Beta 2 +* UserValidator now adds errors to an existing MessageBag instead of replacing it. +* Password reset token will expire after 7 days. +* Added support for custom connections using the $connection attribute of the model. +* Password reset requests are deleted after being used. + +### Version 4.0.0 Beta 1 +* Dropped Ardent dependency. +* Updated to support Laravel 4.2 +* Dropped support for PHP 5.3 +* ConfideUser is going to be a trait+interface from now on. +* Controller generation now also generates an UserRepository class. +* Removed deprecated variables, functions and classes. +* All the codebase has been rewritten. + +__Upgrade note:__ A partial update from previous versions is not recommended. In order to upgrade from v3.* to v4.0.0 the best approach is to update the class names in the providers and aliases array, re-generate the user table with the new migration, re-write the "user" class and finally re-generate the controllers. It's very likely any customization made in your codebase will be affected. + ### Version 3.0.0 Updated to support Laravel 4.1 diff --git a/composer.json b/composer.json index 0d485e9..51d53ed 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,6 @@ "authors": [ { "name": "Zizaco Zizuini", - "email": "zizaco@gmail.com", "homepage": "http://www.zizaco.net" }, { @@ -15,14 +14,16 @@ } ], "require": { - "php": ">=5.3.0", - "illuminate/support": "~4.1", - "laravelbook/ardent": "~2.4" + "php": ">=5.4.0", + "illuminate/support": "~4.2" }, "require-dev": { - "mockery/mockery": "~0.8", - "illuminate/database": "~4.1", - "illuminate/auth": "~4.1" + "illuminate/database": "~4.2", + "illuminate/auth": "~4.2", + "illuminate/console": "~4.2", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "~0.7", + "mockery/mockery": "~0.9" }, "suggest": { "zizaco/entrust":"add Role-based Permissions to Laravel 4" @@ -38,7 +39,8 @@ }, "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-huge-update": "4.0-dev" } - } + }, + "minimum-stability": "dev" } diff --git a/phpunit.xml b/phpunit.xml index 8425e15..d1a5852 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -10,9 +10,15 @@ stopOnFailure="false" syntaxCheck="false" > + + + ./src/Zizaco + ./src/commands + + ./tests/ - \ No newline at end of file + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..f778f56 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,27 @@ + + + + + ./src/Zizaco + ./src/commands + + + + + ./tests/ + + + + + + diff --git a/src/Zizaco/Confide/CacheLoginThrottleService.php b/src/Zizaco/Confide/CacheLoginThrottleService.php new file mode 100644 index 0000000..6f6ac02 --- /dev/null +++ b/src/Zizaco/Confide/CacheLoginThrottleService.php @@ -0,0 +1,106 @@ +app = $app ?: app(); + } + + /** + * Increments the count for the given identity by one and + * also returns the current value for that identity. + * + * @param mixed $identity The login identity + * @return integer How many times that same identity was used + */ + public function throttleIdentity($identity) + { + $identity = $this->parseIdentity($identity); + + // Increments and also retuns the current count + return $this->countThrottle($identity); + } + + /** + * Tells if the given identity has reached the throttle_limit + * @param mixed $identity The login identity + * @return boolean True if the identity has reached the throttle_limit + */ + public function isThrottled($identity) + { + $identity = $this->parseIdentity($identity); + + // Retuns the current count + $count = $this->countThrottle($identity, 0); + + return $count >= $this->app['config']->get('confide::throttle_limit'); + } + + /** + * Parse the given identity in order to return a string with + * the relevant fields. I.E: if the attacker tries to use a + * bunch of different passwords, the identity will still be the + * same. + * @param $mixed $identity + * @return string $identityString + */ + protected function parseIdentity($identity) + { + // If is an array, remove password, remember and then + // transforms it into a string. + if (is_array($identity)) + { + unset($identity['password']); + unset($identity['remember']); + $identity = serialize($identity); + } + + return $identity; + } + + /** + * Increments the count for the given string by one stores + * it into cache and returns the current value for that + * identity. + * + * @param string $identityString + * @param integer $increments Amount that is going to be added to the throttling attemps for the given identity + * @return integer How many times that same string was used + */ + protected function countThrottle($identityString, $increments = 1) + { + $count = $this->app['cache'] + ->get('login_throttling:'.md5($identityString), 0); + + $count = $count + $increments; + + $ttl = $this->app['config']->get('confide::throttle_time_period'); + + $this->app['cache'] + ->put('login_throttling:'.md5($identityString), $count, $ttl); + + return $count; + } +} diff --git a/src/Zizaco/Confide/Confide.php b/src/Zizaco/Confide/Confide.php index 1077296..a91b60e 100644 --- a/src/Zizaco/Confide/Confide.php +++ b/src/Zizaco/Confide/Confide.php @@ -1,52 +1,69 @@ repo = $repo; - $this->app = app(); - } + public $passService; /** - * Returns the Laravel application - * - * @return Illuminate\Foundation\Application + * Confide login throttling service instance + * + * @var \Zizaco\Confide\LoginThrottleServiceInterface + */ + public $loginThrottler; + + /** + * Create a new Confide class + * + * @param \Zizaco\Confide\RepositoryInterface $repo + * @param \Zizaco\Confide\PasswordServiceInterface $passService + * @param \Zizaco\Confide\LoginThrottleServiceInterface $loginThrottler + * @param \Illuminate\Foundation\Application $app Laravel application object + * @return void */ - public function app() + public function __construct( + RepositoryInterface $repo, + PasswordServiceInterface $passService, + LoginThrottleServiceInterface $loginThrottler, + $app = null + ) { - return $this->app; + $this->repo = $repo; + $this->passService = $passService; + $this->loginThrottler = $loginThrottler; + $this->app = $app ?: app(); } /** * Returns an object of the model set in auth config * - * @return object + * @return mixed */ public function model() { @@ -56,7 +73,7 @@ public function model() /** * Get the currently authenticated user or null. * - * @return Zizaco\Confide\ConfideUser|null + * @return \Zizaco\Confide\ConfideUserInterface|null */ public function user() { @@ -64,261 +81,244 @@ public function user() } /** - * Set the user confirmation to true. + * Sets the 'confirmed' field of the user with the + * matching code to true. * * @param string $code - * @return bool + * @return bool Success */ - public function confirm( $code ) + public function confirm($code) { - return $this->repo->confirm( $code ); + return $this->repo->confirmByCode($code); + } + + /** + * Checks if a user with the given identity (email or username) already + * exists and retrieve it + * + * @param array $identity Array containing at least 'username' or 'email'. + * @return \Zizaco\Confide\ConfideUserInterface|null + */ + public function getUserByEmailOrUsername($identity) + { + if (is_array($identity)) + $identity = $this->extractIdentityFromArray($identity); + + return $this->repo->getUserByEmailOrUsername($identity); } /** * Attempt to log a user into the application with * password and identity field(s), usually email or username. * - * @param array $credentials - * @param bool $confirmed_only - * @param mixed $identity_columns + * @param array $input Array containing at least 'username' or 'email' and 'password'. Optionally the 'remember' boolean. + * @param bool $mustBeConfirmed If true, the user must have confirmed his email account in order to log-in. * @return boolean Success */ - public function logAttempt( $credentials, $confirmed_only = false, $identity_columns = array() ) + public function logAttempt($input, $mustBeConfirmed = true) { - // If identity columns is not provided, use all columns of credentials - // except password and remember. - if(empty($identity_columns)) - { - $identity_columns = array_diff( - array_keys($credentials), - array('password','remember') + $remember = $this->extractRememberFromArray($input); + $emailOrUsername = $this->extractIdentityFromArray($input); + + if (!$this->loginThrottling($emailOrUsername)) + return false; + + $user = $this->repo->getUserByEmailOrUsername($emailOrUsername); + + if ($user) { + if (! $user->confirmed && $mustBeConfirmed ) + return false; + + $correctPassword = $this->app['hash']->check( + isset($input['password']) ? $input['password'] : false, + $user->password ); - } - // Check for throttle limit then log-in - if(! $this->reachedThrottleLimit( $credentials ) ) - { - $user = $this->repo->getUserByIdentity($credentials, $identity_columns); - - if( - $user && - ($user->confirmed || ! $confirmed_only ) && - $this->app['hash']->check( - $credentials['password'], - $user->password - ) - ) - { - $remember = isset($credentials['remember']) ? $credentials['remember'] : false; - - $this->app['auth']->login( $user, $remember ); - return true; - } - } + if (! $correctPassword) + return false; - $this->throttleCount( $credentials ); + $this->app['auth']->login($user, $remember); + return true; + } return false; } /** - * Checks if the credentials has been throttled by too - * much failed login attempts - * - * @param array $credentials - * @return mixed Value. + * Extracts the value of the remember key of the given + * array + * @param array $input An array containing the key 'remember' + * @return boolean */ - public function isThrottled( $credentials ) + protected function extractRememberFromArray($input) { - // Check how many failed tries have been done - $attempt_key = $this->attemptCacheKey( $credentials ); - $attempts = $this->app['cache']->get($attempt_key, 0); - - if( $attempts >= $this->app['config']->get('confide::throttle_limit') ) - { - return true; - } - else - { + if (isset($input['remember'])) { + return $input['remember']; + } else { return false; } } /** - * Send email with information about password reset - * - * @param string $email - * @return bool + * Extracts the email or the username key of the given + * array + * @param array $input An array containing the key 'email' or 'username' + * @return mixed */ - public function forgotPassword( $email ) + protected function extractIdentityFromArray($input) { - $user = $this->repo->getUserByMail( $email ); - if( $user ) - { - $user->forgotPassword(); - return true; - } - else - { + if (isset($input['email'])) { + return $input['email']; + } elseif (isset($input['username'])) { + return $input['username']; + } else { return false; } } /** - * Checks to see if the user has a valid token. - * - * @param $token - * @return bool + * Calls throttleIdentity of the loginThrottler and returns false + * if the throttleCount is grater then the 'throttle_limit' config. + * Also sleeps a little in order to avoid dicionary attacks. + * @param mixed $identity + * @return boolean False if the identity has reached the 'throttle_limit' */ - public function isValidToken( $token ) + protected function loginThrottling($identity) { - $count = $this->repo->getPasswordRemindersCount( $token ); - - return ($count != 0); - } + $count = $this->loginThrottler + ->throttleIdentity($identity); - /** - * Change user password - * - * @return string - */ - public function resetPassword( $params ) - { - $token = array_get($params, 'token', ''); - $email = $this->repo->getEmailByReminderToken( $token ); - $user = $this->repo->getUserByMail( $email ); - - if( $user ) - { - if($user->resetPassword( $params )) - { - // Password reset success, remove token from database - $this->repo->deleteEmailByReminderToken( $token ); - - return true; - } - else - { - return false; - } - } - else - { + if ($count >= $this->app['config']->get('confide::throttle_limit')) return false; - } + + // Throttling delay! + // See: http://www.codinghorror.com/blog/2009/01/dictionary-attacks-101.html + if($count > 2) + usleep(($count-1) * 400000); + + return true; } /** - * Log the user out of the application. - * - * @return void + * Asks the loginThrottler service if the given identity has reached the + * throttle_limit + * @param mixed $identity The login identity + * @return boolean True if the identity has reached the throttle_limit */ - public function logout() + public function isThrottled($identity) { - $this->app['auth']->logout(); + return $this->loginThrottler->isThrottled($identity); } /** - * Display the default login view + * If an user with the given email exists then generate + * a token for password change and saves it in the + * 'password_reminders' table with the email of the + * user. * - * @deprecated - * @return Illuminate\View\View + * @param string $email + * @return string $token */ - public function makeLoginForm() + public function forgotPassword($email) { - return $this->app['view']->make($this->app['config']->get('confide::login_form')); + $user = $this->repo->getUserByEmail($email); + + if ($user) + return $this->passService->requestChangePassword($user); + + return false; } /** - * Display the default signup view + * Delete the record of the given token from 'password_reminders' + * table. * - * @deprecated - * @return Illuminate\View\View + * @param string $token Token retrieved from a forgotPassword + * @return boolean Success */ - public function makeSignupForm() + public function destroyForgotPasswordToken($token) { - return $this->app['view']->make( $this->app['config']->get('confide::signup_form') ); + return $this->passService->destroyToken($token); } /** - * Display the forget password view - * - * @deprecated - * @return Illuminate\View\View + * Returns a user that corresponds to the given reset + * password token or false if there is no user with the + * given token. + * @param string $token + * @return ConfideUser */ - public function makeForgotPasswordForm() + public function userByResetPasswordToken($token) { - return $this->app['view']->make( $this->app['config']->get('confide::forgot_password_form') ); + $email = $this->passService->getEmailByToken($token); + + if ($email) { + return $this->repo->getUserByEmail($email); + } + + return false; } /** - * Display the forget password view + * Log the user out of the application. * - * @deprecated - * @return Illuminate\View\View + * @return void */ - public function makeResetPasswordForm( $token ) + public function logout() { - return $this->app['view']->make( $this->app['config']->get('confide::reset_password_form') , array('token'=>$token)); + return $this->app['auth']->logout(); } /** - * Check whether the controller's action exists. - * Returns the url if it does. Otherwise false. - * @param $controllerAction - * @return string + * Display the default login view + * + * @return \Illuminate\View\View */ - public function checkAction( $action, $parameters = array(), $absolute = true ) + public function makeLoginForm() { - try { - $url = $this->app['url']->action($action, $parameters, $absolute); - } catch( InvalidArgumentException $e ) { - return false; - } - - return $url; + return $this->app['view'] + ->make( + $this->app['config']->get('confide::login_form') + ); } /** - * Returns the name of the cache key that will be used - * to store the failed attempts + * Display the default signup view * - * @param array $credentials. - * @return string. + * @return \Illuminate\View\View */ - protected function attemptCacheKey( $credentials ) + public function makeSignupForm() { - return 'confide_flogin_attempt_' - .$this->app['request']->server('REMOTE_ADDR') - .$credentials[$this->app['config']->get('confide::login_cache_field')]; + return $this->app['view'] + ->make( + $this->app['config']->get('confide::signup_form') + ); } /** - * Checks if the current IP / email has reached the throttle - * limit - * - * @param array $credentials - * @return bool Value. + * Display the forget password view + * + * @return \Illuminate\View\View */ - protected function reachedThrottleLimit( $credentials ) + public function makeForgotPasswordForm() { - $attempt_key = $this->attemptCacheKey( $credentials ); - $attempts = $this->app['cache']->get($attempt_key, 0); - - return $attempts >= $this->app['config']->get('confide::throttle_limit'); + return $this->app['view'] + ->make( + $this->app['config']->get('confide::forgot_password_form') + ); } /** - * Increment IP / email throttle count - * - * @param array $credentials - * @return void + * Display the forget password view + * + * @return \Illuminate\View\View */ - protected function throttleCount( $credentials ) + public function makeResetPasswordForm( $token ) { - $attempt_key = $this->attemptCacheKey( $credentials ); - $attempts = $this->app['cache']->get($attempt_key, 0); - - $this->app['cache']->put($attempt_key, $attempts+1, $this->app['config']->get('confide::throttle_time_period')); // used throttling login attempts + return $this->app['view'] + ->make( + $this->app['config']->get('confide::reset_password_form'), + array('token'=>$token) + ); } } diff --git a/src/Zizaco/Confide/ConfideEloquentRepository.php b/src/Zizaco/Confide/ConfideEloquentRepository.php deleted file mode 100644 index ed17758..0000000 --- a/src/Zizaco/Confide/ConfideEloquentRepository.php +++ /dev/null @@ -1,300 +0,0 @@ -app = app(); - } - - /** - * Returns the model set in auth config - * - * @return mixed Instantiated object of the 'auth.model' class - */ - public function model() - { - if (! $this->model) - { - $this->model = $this->app['config']->get('auth.model'); - } - - if(is_object($this->model)) - { - return $this->model; - } - elseif(is_string($this->model)) - { - return new $this->model; - } - - throw new \Exception("Model not specified in config/auth.php", 639); - } - - /** - * Set the user confirmation to true. - * - * @param string $code - * @return bool - */ - public function confirm( $code ) - { - $user = $this->model()->where('confirmation_code', '=', $code)->get()->first(); - if( $user ) - { - return $user->confirm(); - } - else - { - return false; - } - } - - /** - * Find a user by the given email - * - * @param string $email The email to be used in the query - * @return ConfideUser User object - */ - public function getUserByMail( $email ) - { - $user = $this->model()->where('email', '=', $email)->get()->first(); - - return $user; - } - - /** - * Find a user by it's credentials. Perform a 'where' within - * the fields contained in the $identityColumns. - * - * @param array $credentials An array containing the attributes to search for - * @param mixed $identityColumns Array of attribute names or string (for one atribute) - * @return ConfideUser User object - */ - public function getUserByIdentity( $credentials, $identityColumns = array('email') ) - { - if(empty($credentials)) - { - return null; - } - - $identityColumns = (array)$identityColumns; - - $user = $this->model(); - - $first = true; - $hasWhere = false; - - foreach ($identityColumns as $attribute) { - - if(isset($credentials[$attribute])) - { - $hasWhere = true; - - if($first) - { - $user = $user->where($attribute, $credentials[$attribute]); - $first = false; - } - else - { - $user = $user->orWhere($attribute, $credentials[$attribute]); - } - } - else - { - continue; - } - } - - if($hasWhere) - { - $user = $user->get(); - - if(! empty($user)) - { - return $user->first(); - } - - } - - return null; - } - - /** - * Get password reminders count by the given token - * - * @param string $token - * @return int Password reminders count - */ - public function getPasswordRemindersCount( $token ) - { - $count = $this->app['db']->connection()->table('password_reminders') - ->where('token','=',$token)->count(); - - return $count; - } - - /** - * Get email of password reminder by the given token - * - * @param string $token - * @return string Email - */ - public function getEmailByReminderToken( $token ) - { - $email = $this->app['db']->connection()->table('password_reminders') - ->select('email')->where('token','=',$token) - ->first(); - - if ($email && is_object($email)) - { - $email = $email->email; - } - elseif ($email && is_array($email)) - { - $email = $email['email']; - } - - return $email; - } - - /** - * Remove password reminder from database by the given token - * - * @param string $token - * @return void - */ - public function deleteEmailByReminderToken( $token ) - { - $this->app['db']->connection()->table('password_reminders') - ->select('email')->where('token','=',$token) - ->delete(); - } - - /** - * Change the password of the given user. Make sure to hash - * the $password before calling this method. - * - * @param ConfideUser $user An existent user - * @param string $password The password hash to be used - * @return boolean Success - */ - public function changePassword( $user, $password ) - { - $usersTable = $user->getTable(); - $id = $user->getKey(); - $idColumn = $user->getKeyName(); - - $this->app['db']->connection()->table($usersTable) - ->where($idColumn,$id)->update(array('password'=>$password)); - // I.E: DB::table('users')->where('id',3)->update(array('password'=>$password)); - - return true; - } - - /** - * Generate a token for password change and saves it in - * the 'password_reminders' table with the email of the - * user. - * - * @param ConfideUser $user An existent user - * @return string Password reset token - */ - public function forgotPassword( $user ) - { - $token = md5( uniqid(mt_rand(), true) ); - - $values = array( - 'email'=> $user->email, - 'token'=> $token, - 'created_at'=> new \DateTime - ); - - $this->app['db']->connection()->table('password_reminders') - ->insert( $values ); - // I.E: - // DB::table('password_reminders')->insert(array( - // 'email'=> $this->email, - // 'token'=> $token, - // 'created_at'=> new \DateTime - //)); - - return $token; - } - - /** - * Checks if an non saved user has duplicated credentials - * (email and/or username) - * - * @param ConfideUser $user The non-saved user to be checked - * @return int The number of duplicated entry founds. Probably 0 or 1. - */ - public function userExists( $user ) - { - $usersTable = $user->getTable(); - - $query = $this->app['db']->connection()->table($usersTable) - ->where('email',$user->email); - - if($user->username) - { - $query = $query->orWhere('username',$user->username); - } - - $count = $query->count(); - // I.E: DB::table('users')->where('email', 'bob@sample.com')->orWhere('username', 'bob')->count(); - - return $count; - } - - /** - * Set the 'confirmed' column of the given user to 1 - * - * @param ConfideUser $user An existent user - * @return boolean Success - */ - public function confirmUser( $user ) - { - $usersTable = $user->getTable(); - $id = $user->getKey(); - $idColumn = $user->getKeyName(); - - $this->app['db']->connection()->table($usersTable) - ->where($idColumn,$id)->update(array('confirmed'=>1)); - // I.E: DB::table('users')->where('id',3)->update(array('confirmed'=>1)); - - return true; - } - - public function validate($user, array $rules = array(), array $customMessages = array()) - { - return $user->validate($rules, $customMessages); - } - -} diff --git a/src/Zizaco/Confide/ConfideRepository.php b/src/Zizaco/Confide/ConfideRepository.php deleted file mode 100644 index 738a135..0000000 --- a/src/Zizaco/Confide/ConfideRepository.php +++ /dev/null @@ -1,104 +0,0 @@ -package('zizaco/confide'); - } - - /** - * Register the service provider. - * - * @return void - */ - public function register() - { - $this->registerRepository(); - - $this->registerConfide(); - - $this->registerCommands(); - } - - /** - * Register the repository that will handle all the database interaction. - * - * @return void - */ - protected function registerRepository() - { - $this->app->bind('confide.repository', function($app) - { - return new ConfideEloquentRepository; - }); - } - - /** - * Register the application bindings. - * - * @return void - */ - protected function registerConfide() - { - $this->app->bind('confide', function($app) - { - return new Confide($app->make('confide.repository')); - }); - } - - /** - * Register the artisan commands. - * - * @return void - */ - protected function registerCommands() - { - $this->app['command.confide.controller'] = $this->app->share(function($app) - { - return new ControllerCommand($app); - }); - - $this->app['command.confide.routes'] = $this->app->share(function($app) - { - return new RoutesCommand($app); - }); - - $this->app['command.confide.migration'] = $this->app->share(function($app) - { - return new MigrationCommand($app); - }); - - $this->commands( - 'command.confide.controller', - 'command.confide.routes', - 'command.confide.migration' - ); - } - -} diff --git a/src/Zizaco/Confide/ConfideUser.php b/src/Zizaco/Confide/ConfideUser.php index 655651b..d16afa3 100644 --- a/src/Zizaco/Confide/ConfideUser.php +++ b/src/Zizaco/Confide/ConfideUser.php @@ -1,159 +1,118 @@ confirmed = true; - /** - * This way the model will automatically replace the plain-text password - * attribute (from $passwordAttributes) with the hash checksum on save - * - * @var bool - */ - public $autoHashPasswordAttributes = true; + return ConfideFacade::confirm($this->confirmation_code); + } /** - * Ardent validation rules + * Generates a token for password change and saves it in the + * 'password_reminders' table with the email of the + * user. * - * @var array - */ - public static $rules = array( - 'username' => 'required|alpha_dash|unique:users', - 'email' => 'required|email|unique:users', - 'password' => 'required|min:4|confirmed', - 'password_confirmation' => 'min:4', - ); - - /** - * Create a new ConfideUser instance. + * @return string $token */ - public function __construct( array $attributes = array() ) + public function forgotPassword() { - parent::__construct( $attributes ); - - if ( ! static::$app ) - static::$app = app(); - - $this->table = static::$app['config']->get('auth.table'); + return ConfideFacade::forgotPassword($this->email); } /** - * Get the unique identifier for the user. + * Checks if the current user is valid using the ConfideUserValidator * - * @return mixed + * @return boolean */ - public function getAuthIdentifier() + public function isValid() { - return $this->getKey(); + // Instantiate the Zizaco\Confide\UserValidator and calls the + // validate method. Feel free to use your own validation + // class. + $validator = App::make('confide.user_validator'); + + return $validator->validate($this); } /** - * Get the password for the user. + * Overwrites the original save method in order to perform + * validation before actually saving the object. * - * @return string + * @param array $options + * @return bool */ - public function getAuthPassword() + public function save(array $options = array()) { - return $this->password; + if ($this->isValid()) { + return parent::save($options); + } else { + return false; + } } /** - * Confirm the user (usually means that the user) - * email is valid. + * Returns a MessageBag object that store any error + * regarding the user validation * - * @return bool + * @var \Illuminate\Support\MessageBag */ - public function confirm() + public function errors() { - $this->confirmed = 1; - - // ConfideRepository will update the database - static::$app['confide.repository'] - ->confirmUser( $this ); + if (!$this->errors) + $this->errors = App::make('Illuminate\Support\MessageBag'); - return true; + return $this->errors; } /** - * Send email with information about password reset + * Get the unique identifier for the user. * - * @return string + * @see \Illuminate\Auth\UserInterface + * @return mixed */ - public function forgotPassword() + public function getAuthIdentifier() { - // ConfideRepository will generate token (and save it into database) - $token = static::$app['confide.repository'] - ->forgotPassword( $this ); - - $view = static::$app['config']->get('confide::email_reset_password'); - - $this->sendEmail( 'confide::confide.email.password_reset.subject', $view, array('user'=>$this, 'token'=>$token) ); - - return true; + // Get the value of the model's primary key. + return $this->getKey(); } /** - * Change user password + * Get the password for the user. * - * @param $params + * @see \Illuminate\Auth\UserInterface * @return string */ - public function resetPassword( $params ) + public function getAuthPassword() { - $password = array_get($params, 'password', ''); - $passwordConfirmation = array_get($params, 'password_confirmation', ''); - - $passwordValidators = array( - 'password' => static::$rules['password'], - 'password_confirmation' => static::$rules['password_confirmation'], - ); - $user = static::$app['confide.repository']->model(); - $user->unguard(); - $user->fill(array( - 'password' => $password, - 'password_confirmation' => $passwordConfirmation, - )); - $user->reguard(); - $validationResult = static::$app['confide.repository']->validate($user, $passwordValidators); - - if ( $validationResult ) - { - return static::$app['confide.repository'] - ->changePassword( $this, static::$app['hash']->make($password) ); - } - else{ - return false; - } + return $this->password; } /** @@ -191,343 +150,13 @@ public function getRememberTokenName() } /** - * Overwrite the Ardent save method. Saves model into - * database - * - * @param array $rules:array - * @param array $customMessages - * @param array $options - * @param \Closure $beforeSave - * @param \Closure $afterSave - * @return bool - */ - public function save( array $rules = array(), array $customMessages = array(), array $options = array(), \Closure $beforeSave = null, \Closure $afterSave = null ) - { - $duplicated = false; - - /* - * When EloquentUserProvider call updateRememberToken - * it doesn't retrieve rules, so validation on Ardent fails - */ - if (!empty($this->remember_token) && empty($rules)) - { - $rules = static::$rules; - $rules = array_diff(array_keys($rules), array('password_confirmation')); - } - - if(! $this->getKey()) - { - $duplicated = static::$app['confide.repository']->userExists( $this ); - } - - if(! $duplicated) - { - return $this->real_save( $rules, $customMessages, $options, $beforeSave, $afterSave ); - } - else - { - $this->validationErrors->add( - 'duplicated', - static::$app['translator']->get('confide::confide.alerts.duplicated_credentials') - ); - - return false; - } - } - - /** - * Ardent method overloading: - * Before save the user. Generate a confirmation - * code if is a new user. - * - * @param bool $forced - * @return bool - */ - public function beforeSave($forced = false) - { - $id=$this->getKey(); - if ( empty($id) ) - { - $this->confirmation_code = md5( uniqid(mt_rand(), true) ); - } - - /* - * Remove password_confirmation field before save to - * database. - */ - if ( isset($this->password_confirmation) ) - { - unset( $this->password_confirmation ); - } - - return true; - } - - /** - * Ardent method overloading: - * After save, delivers the confirmation link email. - * code if is a new user. - * - * @param $success - * @param bool $forced - * @return bool - */ - public function afterSave($success=true, $forced = false) - { - if (! $this->confirmed && ! static::$app['cache']->get('confirmation_email_'.$this->getKey()) ) - { - // on behalf or the config file we should send and email or not - if (static::$app['config']->get('confide::signup_email') == true) - { - $view = static::$app['config']->get('confide::email_account_confirmation'); - $this->sendEmail( 'confide::confide.email.account_confirmation.subject', $view, array('user' => $this) ); - } - // Save in cache that the email has been sent. - $signup_cache = (int)static::$app['config']->get('confide::signup_cache'); - if ($signup_cache !== 0) - { - static::$app['cache']->put('confirmation_email_'.$this->getKey(), true, $signup_cache); - } - } - - return true; - } - - /** - * Runs the real eloquent save method or returns - * true if it's under testing. Because Eloquent - * and Ardent save methods are not Confide's - * responsibility. + * Get the e-mail address where password reminders are sent. * - * @param array $rules - * @param array $customMessages - * @param array $options - * @param \Closure $beforeSave - * @param \Closure $afterSave - * @return bool - */ - protected function real_save( array $rules = array(), array $customMessages = array(), array $options = array(), \Closure $beforeSave = null, \Closure $afterSave = null ) - { - if ( defined('CONFIDE_TEST') ) - { - $this->beforeSave(); - $this->afterSave(true); - return true; - } - else{ - - /* - * This will make sure that a non modified password - * will not trigger validation error. - * @fixed Pull #110 - */ - if( isset($rules['password']) && $this->password == $this->getOriginal('password') ) - { - unset($rules['password']); - unset($rules['password_confirmation']); - } - - return parent::save( $rules, $customMessages, $options, $beforeSave, $afterSave ); - } - } - - /** - * Add the namespace 'confide::' to view hints. - * this makes possible to send emails using package views from - * the command line. - * - * @return void - */ - protected static function fixViewHint() - { - if (isset(static::$app['view.finder'])) - static::$app['view.finder']->addNamespace('confide', __DIR__.'/../../views'); - } - - /** - * Send email using the lang sentence as subject and the viewname - * - * @param mixed $subject_translation - * @param mixed $view_name - * @param array $params - * @return voi. - */ - protected function sendEmail( $subject_translation, $view_name, $params = array() ) - { - if ( static::$app['config']->getEnvironment() == 'testing' ) - return; - - static::fixViewHint(); - - $user = $this; - - static::$app['mailer']->send($view_name, $params, function($m) use ($subject_translation, $user) - { - $m->to( $user->email ) - ->subject( ConfideUser::$app['translator']->get($subject_translation) ); - }); - } - - /* - |-------------------------------------------------------------------------- - | Deprecated variables and methods - |-------------------------------------------------------------------------- - | - */ - - /** - * Rules for when updating a user. - * - * @deprecated - * @var array - */ - protected $updateRules = array( - 'username' => 'required|alpha_dash', - 'email' => 'required|email', - 'password' => 'min:4|confirmed', - 'password_confirmation' => 'min:4', - ); - - /** - * Alias of save but uses updateRules instead of rules. - * @deprecated use updateUnique() instead - * @param array $rules - * @param array $customMessages - * @param array $options - * @param Closure $beforeSave - * @param Closure $afterSave - * @return bool - */ - public function amend( array $rules = array(), array $customMessages = array(), array $options = array(), \Closure $beforeSave = null, \Closure $afterSave = null ) - { - if (empty($rules)) { - $rules = $this->getUpdateRules(); - } - return $this->save( $rules, $customMessages, $options, $beforeSave, $afterSave ); - } - - /** - * [Deprecated] Generates UUID and checks it for uniqueness against a table/column. - * - * @deprecated - * @param $table - * @param $field + * @see \Illuminate\Auth\Reminders\RemindableInterface * @return string */ - protected function generateUuid($table, $field) - { - return md5( uniqid(mt_rand(), true) ); - } - - /** - * [Deprecated] - * - * @deprecated - */ - public function getUpdateRules() - { - return $this->updateRules; - } - - /** - * [Deprecated] Parses the two given users and compares the unique fields. - * - * @deprecated - * @param $oldUser - * @param $updatedUser - * @param array $rules - */ - public function prepareRules($oldUser, $updatedUser, $rules=array()) - { - if(empty($rules)) { - $rules = $this->getRules(); - } - - foreach($rules as $rule => $validation) { - // get the rules with unique. - if (strpos($validation, 'unique')) { - // Compare old vs new - if($oldUser->$rule != $updatedUser->$rule) { - // Set update rule to creation rule - $updateRules = $this->getUpdateRules(); - $updateRules[$rule] = $validation; - $this->setUpdateRules($updateRules); - } - } - } - } - - /** - * [Deprecated] - * - * @deprecated - */ - public function getRules() - { - return self::$rules; - } - - /** - * [Deprecated] - * - * @deprecated - */ - public function setUpdateRules($set) - { - $this->updateRules = $set; - } - - /** - * [Deprecated] Find an user by it's credentials. Perform a 'where' within - * the fields contained in the $identityColumns. - * - * @deprecated Use ConfideRepository getUserByIdentity instead. - * @param array $credentials An array containing the attributes to search for - * @param mixed $identityColumns Array of attribute names or string (for one atribute) - * @return ConfideUser User object - */ - public function getUserFromCredsIdentity($credentials, $identity_columns = array('username', 'email')) - { - return static::$app['confide.repository']->getUserByIdentity($credentials, $identity_columns); - } - - /** - * [Deprecated] Checks if an user exists by it's credentials. Perform a 'where' within - * the fields contained in the $identityColumns. - * - * @deprecated Use ConfideRepository getUserByIdentity instead. - * @param array $credentials An array containing the attributes to search for - * @param mixed $identityColumns Array of attribute names or string (for one atribute) - * @return boolean Exists? - */ - public function checkUserExists($credentials, $identity_columns = array('username', 'email')) + public function getReminderEmail() { - $user = static::$app['confide.repository']->getUserByIdentity($credentials, $identity_columns); - - if ($user) { - return true; - } else { - return false; - } - } - - /** - * [Deprecated] Checks if an user is confirmed by it's credentials. Perform a 'where' within - * the fields contained in the $identityColumns. - * - * @deprecated Use ConfideRepository getUserByIdentity instead. - * @param array $credentials An array containing the attributes to search for - * @param mixed $identityColumns Array of attribute names or string (for one atribute) - * @return boolean Is confirmed? - */ - public function isConfirmed($credentials, $identity_columns = array('username', 'email')) - { - $user = static::$app['confide.repository']->getUserByIdentity($credentials, $identity_columns); - - if (! is_null($user) and $user->confirmed) { - return true; - } else { - return false; - } + return $this->email; } } diff --git a/src/Zizaco/Confide/ConfideUserInterface.php b/src/Zizaco/Confide/ConfideUserInterface.php new file mode 100644 index 0000000..d049eb6 --- /dev/null +++ b/src/Zizaco/Confide/ConfideUserInterface.php @@ -0,0 +1,40 @@ +app = $app ?: app(); + } + + /** + * Generate a token for password change and saves it in + * the 'password_reminders' table with the email of the + * user. + * + * @param RemindableInterface $user An existent user + * @return string Password reset token + */ + public function requestChangePassword(RemindableInterface $user) + { + $email = $user->getReminderEmail(); + $token = $this->generateToken(); + + $values = array( + 'email'=> $email, + 'token'=> $token, + 'created_at'=> new \DateTime + ); + + $this->app['db'] + ->connection($user->getConnectionName()) + ->table('password_reminders') + ->insert($values); + + $this->sendEmail($user, $token); + + return $token; + } + + /** + * Returns the email associated with the given reset + * password token + * @param string $token + * @return string Email + */ + public function getEmailByToken($token) + { + $connection = $this->getConnection(); + + $email = $this->app['db'] + ->connection($connection) + ->table('password_reminders') + ->select('email') + ->where('token','=',$token) + ->where('created_at','>=',$this->getOldestValidDate()) + ->first(); + + $email = $this->unwrapEmail($email); + + return $email; + } + + /** + * Delete the record of the given token from database + * + * @param string $token + * @return boolean Success + */ + public function destroyToken($token) + { + $connection = $this->getConnection(); + + $affected = $this->app['db'] + ->connection($connection) + ->table('password_reminders') + ->where('token','=',$token) + ->delete(); + + return $affected > 0; + } + + /** + * Returns a possible custom connection that may has being used + * for the user model. If null is returned by this method than + * the default connection is going to be used. + * + * @return string Original $connection value of the user model. + */ + protected function getConnection() + { + return $this->app['confide.repository'] + ->model()->getConnectionName(); + } + + /** + * Generates a random password change token + * + * @return string + */ + protected function generateToken() + { + return md5(uniqid(mt_rand(), true)); + } + + /** + * Extracts the email of the given object or array + * @param mixed $email An object, array or email string + * @return string The email address + */ + protected function unwrapEmail($email) + { + if ($email && is_object($email)) + { + $email = $email->email; + } + elseif ($email && is_array($email)) + { + $email = $email['email']; + } + + return $email; + } + + /** + * Sends an email containing the reset password link with the + * given token to the user + * + * @param RemindableInterface $user An existent user + * @param string $token Password reset token + * @return void + */ + protected function sendEmail($user, $token) + { + $config = $this->app['config']; + $lang = $this->app['translator']; + + $this->app['mailer']->send($config->get('confide::email_reset_password'), compact('user', 'token'), function($message) use ($user, $token, $lang) { + $message + ->to($user->email, $user->username) + ->subject($lang->get('confide::confide.email.password_reset.subject')); + }); + } + + /** + * Returns a date to limit the acceptable password reset + * requests. + * + * @return string 'Y-m-d H:i:s' formated string + */ + protected function getOldestValidDate() + { + // Instantiate a carbon object (that is a dependency of + // 'illuminate/database') + $carbon = $this->app['Carbon\Carbon']; + $config = $this->app['config']; + + return $carbon->now() + ->subHours($config->get('confide::confide.password_reset_expiration', 7)) + ->toDateTimeString(); + } +} diff --git a/src/Zizaco/Confide/EloquentRepository.php b/src/Zizaco/Confide/EloquentRepository.php new file mode 100644 index 0000000..4eebeb2 --- /dev/null +++ b/src/Zizaco/Confide/EloquentRepository.php @@ -0,0 +1,144 @@ +app = $app ?: app(); + } + + /** + * Returns the model set in auth config + * + * @return mixed Instantiated object of the 'auth.model' class + */ + public function model() + { + if (! $this->model) { + $this->model = $this->app['config']->get('auth.model'); + } + + if ($this->model) { + return $this->app[$this->model]; + } + + throw new \Exception("Wrong model specified in config/auth.php", 639); + } + + /** + * Find a user by one of the fields given as $identity. + * If one of the fields in the $identity array matches the user + * will be retrieved. + * @param array $identity An array of attributes and values to search for + * @return ConfideUser User object + */ + public function getUserByIdentity($identity) + { + $user = $this->model(); + + $firstWhere = true; + foreach ($identity as $attribute => $value) { + + if ($firstWhere) { + $user = $user->where($attribute, '=', $value); + } else { + $user = $user->orWhere($attribute, '=', $value); + } + + $firstWhere = false; + } + + $user = $user->get()->first(); + + return $user; + } + + /** + * Find a user by the given email + * + * @param string $email The email to be used in the query + * @return ConfideUser User object + */ + public function getUserByEmail($email) + { + return $this->getUserByIdentity(['email'=>$email]); + } + + /** + * Find a user by the given email or username + * + * @param string $emailOrUsername Username of email to be used in the query + * @return ConfideUser User object + */ + public function getUserByEmailOrUsername($emailOrUsername) + { + $identity = [ + 'email' => $emailOrUsername, + 'username' => strtolower($emailOrUsername) + ]; + + return $this->getUserByIdentity($identity); + } + + /** + * Update the confirmation status of a user to true if a user + * is found with the given confirmation code. + * + * @param string $code + * @return bool Success + */ + public function confirmByCode($code) + { + $identity = ['confirmation_code' => $code]; + + $user = $this->getUserByIdentity($identity); + + if ($user) { + return $this->confirmUser($user); + } else { + return false; + } + } + + /** + * Updated the given user in the database. Set the 'confirmed' attribute to + * true. + * @param ConfideUser User object + * @return bool Success + */ + protected function confirmUser($user) + { + $user->confirmed = true; + + return $user->save(); + } +} diff --git a/src/Zizaco/Confide/ConfideFacade.php b/src/Zizaco/Confide/Facade.php similarity index 55% rename from src/Zizaco/Confide/ConfideFacade.php rename to src/Zizaco/Confide/Facade.php index cb78b4d..c398342 100644 --- a/src/Zizaco/Confide/ConfideFacade.php +++ b/src/Zizaco/Confide/Facade.php @@ -1,6 +1,12 @@ package('zizaco/confide'); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->registerRepository(); + + $this->registerPasswordService(); + + $this->registerLoginThrottleService(); + + $this->registerUserValidator(); + + $this->registerConfide(); + + $this->registerCommands(); + } + + /** + * Register the repository that will handle all the database + * interaction. + * + * @return void + */ + protected function registerRepository() + { + $this->app->bind('confide.repository', function($app) + { + return new EloquentRepository($app); + }); + } + + /** + * Register the service that abstracts all user password management + * related methods + * + * @return void + */ + protected function registerPasswordService() + { + $this->app->bind('confide.password', function($app) + { + return new EloquentPasswordService($app); + }); + } + + /** + * Register the service that Throttles login after + * too many failed attempts. This is a secure measure in + * order to avoid brute force attacks. + * + * @return void + */ + protected function registerLoginThrottleService() + { + $this->app->bind('confide.throttle', function($app) + { + return new CacheLoginThrottleService($app); + }); + } + + /** + * Register the UserValidator class. The default class that + * used for user validation + * + * @return void + */ + protected function registerUserValidator() + { + $this->app->bind('confide.user_validator', function($app) + { + return new UserValidator(); + }); + } + + /** + * Register the application bindings. + * + * @return void + */ + protected function registerConfide() + { + $this->app->bind('confide', function($app) + { + return new Confide( + $app->make('confide.repository'), + $app->make('confide.password'), + $app->make('confide.throttle'), + $app + ); + }); + } + + /** + * Register the artisan commands. + * + * @return void + */ + protected function registerCommands() + { + $this->app->bind('command.confide.controller', function($app) + { + return new ControllerCommand($app); + }); + + $this->app->bind('command.confide.routes', function($app) + { + return new RoutesCommand($app); + }); + + $this->app->bind('command.confide.migration', function($app) + { + return new MigrationCommand($app); + }); + + $this->commands( + 'command.confide.controller', + 'command.confide.routes', + 'command.confide.migration' + ); + } +} diff --git a/src/Zizaco/Confide/Support/GenerateCommand.php b/src/Zizaco/Confide/Support/GenerateCommand.php new file mode 100644 index 0000000..2d70b77 --- /dev/null +++ b/src/Zizaco/Confide/Support/GenerateCommand.php @@ -0,0 +1,104 @@ +app = $app ?: app(); + } + + /** + * Generates the given file with the rendered view + * @param string $filename Path to the file within the app directory + * @param string $view View file + * @param array $viewVars Variables that are going to be passed to the view + * @return bool Success + */ + protected function generateFile($filename, $view, $viewVars) + { + $output = $this->app['view']->make('confide::'.$view, $viewVars) + ->render(); + + $filename = $this->app['path'].'/'.trim($filename,'/'); + $directory = dirname($filename); + + $this->makeDir($directory, 0755, true); + $this->filePutContents($filename, $output); + + return true; + } + + /** + * Append the rendered view to the given file. Same as generateFile but + * the 'file_put_contents' is called with the FILE_APPEND flag. + * @param string $filename Path to the file within the app directory + * @param string $view View file + * @param array $viewVars Variables that are going to be passed to the view + * @return bool Success + */ + protected function appendInFile($filename, $view, $viewVars) + { + $output = $this->app['view']->make('confide::'.$view, $viewVars) + ->render(); + + $filename = $this->app['path'].'/'.trim($filename,'/'); + $directory = dirname($filename); + + $this->makeDir($directory, 0755, true); + $this->filePutContents($filename, $output, FILE_APPEND); + + return true; + } + + /** + * Encapsulates mkdir function + * @codeCoverageIgnore + * @param string $directory + * @param int $mode + * @param bool $recursive + * @return void + */ + protected function makeDir($directory, $mode, $recursive) + { + if (! is_dir($directory)) + @mkdir($directory, $mode, $recursive); + } + + /** + * Encapsulates file_put_contents function + * @codeCoverageIgnore + * @param string $filename + * @param string $data + * @param int $flags + * @return void + */ + protected function filePutContents($filename, $data, $flags = 0) + { + @file_put_contents($filename, $data, $flags); + } +} diff --git a/src/Zizaco/Confide/UserValidator.php b/src/Zizaco/Confide/UserValidator.php new file mode 100644 index 0000000..c8ea445 --- /dev/null +++ b/src/Zizaco/Confide/UserValidator.php @@ -0,0 +1,188 @@ + [ + 'username' => 'required|alpha_dash', + 'email' => 'required|email', + 'password' => 'required|min:4', + ], + 'update' => [ + 'username' => 'required|alpha_dash', + 'email' => 'required|email', + 'password' => 'required|min:4', + ] + ]; + + /** + * Validates the given user. Should check if all the fields are correctly + * @param ConfideUserInterface $user Instance to be tested + * @return boolean True if the $user is valid + */ + public function validate(ConfideUserInterface $user, $ruleset = 'create') + { + // Set the $repo as a ConfideRepository object + $this->repo = App::make('confide.repository'); + + // Validate object + $result = $this->validateAttributes($user, $ruleset) ? true : false; + $result = ($this->validatePassword($user) && $result) ? true : false; + $result = ($this->validateIsUnique($user) && $result) ? true : false; + + return $result; + } + + /** + * Validates the password and password_confirmation of the given + * user + * @param ConfideUserInterface $user + * @return boolean True if password is valid + */ + public function validatePassword(ConfideUserInterface $user) + { + $hash = App::make('hash'); + + if($user->getOriginal('password') != $user->password) { + if ($user->password == $user->password_confirmation) { + + // Hashes password and unset password_confirmation field + $user->password = $hash->make($user->password); + unset($user->password_confirmation); + + return true; + } else { + $this->attachErrorMsg( + $user, + 'validation.confirmed::confide.alerts.wrong_confirmation', + 'password_confirmation' + ); + return false; + } + } + + return true; + } + + /** + * Validates if the given user is unique. If there is another + * user with the same credentials but a different id, this + * method will return false. + * @param ConfideUserInterface $user + * @return boolean True if user is unique + */ + public function validateIsUnique(ConfideUserInterface $user) + { + $identity = [ + 'email' => $user->email, + 'username' => $user->username, + ]; + + foreach($identity as $attribute => $value) { + + $similar = $this->repo->getUserByIdentity([$attribute => $value]); + + if (!$similar || $similar->getKey() == $user->getKey()) { + unset($identity[$attribute]); + } else { + $this->attachErrorMsg( + $user, 'confide::confide.alerts.duplicated_credentials', $attribute + ); + } + + } + + if(!$identity) { + return true; + } + + return false; + } + + /** + * Uses Laravel Validator in order to check if the attributes of the + * $user object are valid for the given $ruleset + * @param ConfideUserInterface $user + * @param string $ruleset The name of the key in the UserValidator->$rules array + * @return boolean True if the attributes are valid + */ + public function validateAttributes(ConfideUserInterface $user, $ruleset = 'create') + { + $attributes = $user->toArray(); + + // Force getting password since it may be hidden from array form + $attributes['password'] = $user->getAuthPassword(); + + $rules = $this->rules[$ruleset]; + + $validator = App::make('validator') + ->make( $attributes, $rules ); + + // Validate and attach errors + if ($validator->fails()) { + $user->errors = $validator->errors(); + return false; + } else { + return true; + } + } + + /** + * Creates a \Illuminate\Support\MessageBag object, add the error message + * to it and then set the errors attribute of the user with that bag + * @param ConfideUserInterface $user + * @param string $errorMsg The error message + * @param string $key The key if the error message + * @return void + */ + public function attachErrorMsg(ConfideUserInterface $user, $errorMsg, $key = 'confide') + { + $messageBag = $user->errors; + + if (! $messageBag instanceof MessageBag) { + $messageBag = App::make('Illuminate\Support\MessageBag'); + } + + $messageBag->add($key, Lang::get($errorMsg)); + $user->errors = $messageBag; + } +} diff --git a/src/Zizaco/Confide/UserValidatorInterface.php b/src/Zizaco/Confide/UserValidatorInterface.php new file mode 100644 index 0000000..361dafa --- /dev/null +++ b/src/Zizaco/Confide/UserValidatorInterface.php @@ -0,0 +1,21 @@ +addNamespace('confide',substr(__DIR__,0,-8).'views'); + return array( + array('name', null, InputOption::VALUE_OPTIONAL, 'Name of the controller.', 'Users'), + array('--restful', '-r', InputOption::VALUE_NONE, 'Generate RESTful controller.') + ); } /** @@ -39,68 +47,55 @@ public function __construct() */ public function fire() { - $name = $this->prepareName($this->option('name')); + // Prepare variables + $class = $this->getControllerName($this->option('name')); + $namespace = $this->getNamespace($this->option('name')); + $model = $this->app['config']->get('auth.model'); $restful = $this->option('restful'); - $this->line(''); - $this->info( "Controller name: $name".(($restful) ? "\nRESTful: Yes" : '') ); - $message = "An authentication ".(($restful) ? 'RESTful ' : '')."controller template with the name $name.php". - " will be created in app/controllers directory and will NOT overwrite any ". - " file."; + $viewVars = compact( + 'class','namespace','model','restful' + ); - $this->comment( $message ); + // Prompt + $this->line(''); + $this->info("Controller name: $class".(($restful) ? "\nRESTful: Yes" : '') ); + $this->comment("An authentication ".(($restful) ? 'RESTful ' : '')."controller template with the name ".($namespace ? $namespace.'\\' : '')."$class.php". + " will be created in app/controllers directory"); $this->line(''); if ( $this->confirm("Proceed with the controller creation? [Yes|no]") ) { - $this->line(''); - - $this->info( "Creating $name..." ); - if( $this->createController( $name, $restful ) ) - { - $this->info( "$name.php Successfully created!" ); - } - else{ - $this->error( - "Coudn't create app/controllers/$name.php.\nCheck the". - " write permissions within the controllers directory". - " or if $name.php already exists. (This command will". - " not overwrite any file. delete the existing $name.php". - " if you wish to continue)." - ); - } - - $this->line(''); - + $this->info( "Creating $class..." ); + // Generate + $filename = 'controllers/'.($namespace ? str_replace('\\', '/', $namespace).'/' : '').$class.'.php'; + $this->generateFile($filename, 'generators.controller', $viewVars); + $this->info( "$class.php Successfully created!" ); + + // Generate repository + $filename = 'models/'.str_replace('\\', '/', $model).'Repository.php'; + $this->generateFile($filename, 'generators.repository', $viewVars); + $this->info( $model.'Repository.php Successfully created!'); } } /** - * Get the console command options. - * - * @return array + * Returns the name of the controller class that will handle a + * resource with the given name. + * @param string $name Resource name + * @return string Controller class name */ - protected function getOptions() + protected function getControllerName($name) { - $app = app(); + if (strstr($name, '\\')) + { + $name = explode('\\', $name); + $name = array_pop($name); + } - return array( - array('name', null, InputOption::VALUE_OPTIONAL, 'Name of the controller.', $app['config']->get('auth.model')), - array('--restful', '-r', InputOption::VALUE_NONE, 'Generate RESTful controller.'), - ); - } + $name = ( $name != '') ? ucfirst($name) : 'Users'; - /** - * Prepare the controller name - * - * @param string $name - * @return string - */ - protected function prepareName($name = '') - { - $name = ( $name != '') ? ucfirst($name) : 'User'; - - if( substr($name,-10) == 'controller' ) + if( substr(strtolower($name),-10) == 'controller' ) { $name = substr($name, 0, -10).'Controller'; } @@ -113,39 +108,21 @@ protected function prepareName($name = '') } /** - * Create the controller - * - * @param string $name - * @return bool + * Returns the namespace of the given class name + * @param string $name Class name + * @return string Namespace */ - protected function createController( $name = '', $restful = false ) + protected function getNamespace($name) { - $app = app(); - - $controller_file = $this->laravel->path."/controllers/$name.php"; - $output = $app['view']->make('confide::generators.controller') - ->with('name', $name) - ->with('restful', $restful) - ->render(); - - if( ! file_exists( $controller_file ) ) - { - $fs = fopen($controller_file, 'x'); - if ( $fs ) - { - fwrite($fs, $output); - fclose($fs); - return true; - } - else - { - return false; - } - } - else + if (strstr($name, '\\')) { - return false; + $name = explode('\\', $name); + array_pop($name); + $name = implode('\\', $name); + } else { + $name = ''; } - } + return $name; + } } diff --git a/src/commands/MigrationCommand.php b/src/commands/MigrationCommand.php index 20f027d..9c53139 100644 --- a/src/commands/MigrationCommand.php +++ b/src/commands/MigrationCommand.php @@ -1,11 +1,17 @@ addNamespace('confide',substr(__DIR__,0,-8).'views'); + return array( + array('table', null, InputOption::VALUE_OPTIONAL, 'Table name.', 'users'), + ); } /** @@ -39,81 +45,29 @@ public function __construct() */ public function fire() { + // Prepare variables $table = lcfirst($this->option('table')); + $viewVars = compact( + 'table' + ); + + // Prompt $this->line(''); $this->info( "Table name: $table" ); - $message = "A migration that creates the $table table will". - " be created in app/database/migrations directory"; - - $this->comment( $message ); + $this->comment("A migration that creates the $table table will". + " be created in app/database/migrations directory"); $this->line(''); if ( $this->confirm("Proceed with the migration creation? [Yes|no]") ) { - $this->line(''); - $this->info( "Creating migration..." ); - if( $this->createMigration( $table ) ) - { - $this->info( "Migration successfully created!" ); - } - else{ - $this->error( - "Coudn't create migration.\n Check the write permissions". - " within the app/database/migrations directory." - ); - } - - $this->line(''); - - } - } - - /** - * Get the console command options. - * - * @return array - */ - protected function getOptions() - { - $app = app(); - - return array( - array('table', null, InputOption::VALUE_OPTIONAL, 'Table name.', $app['config']->get('auth.table')), - ); - } + // Generate + $filename = 'database/migrations/'. + date('Y_m_d_His')."_confide_setup_users_table.php"; + $this->generateFile($filename, 'generators.migration', $viewVars); - /** - * Create the migration - * - * @param string $name - * @return bool - */ - protected function createMigration( $table = 'users' ) - { - $app = app(); - $migration_file = $this->laravel->path."/database/migrations/".date('Y_m_d_His')."_confide_setup_users_table.php"; - $output = $app['view']->make('confide::generators.migration')->with('table', $table)->render(); - - if( ! file_exists( $migration_file ) ) - { - $fs = fopen($migration_file, 'x'); - if ( $fs ) - { - fwrite($fs, $output); - fclose($fs); - return true; - } - else - { - return false; - } - } - else - { - return false; + $this->info( "Migration successfully created!" ); } } - } diff --git a/src/commands/RoutesCommand.php b/src/commands/RoutesCommand.php index 68c2df3..25eef97 100644 --- a/src/commands/RoutesCommand.php +++ b/src/commands/RoutesCommand.php @@ -1,11 +1,18 @@ addNamespace('confide',substr(__DIR__,0,-8).'views'); + return array( + array('controller', null, InputOption::VALUE_OPTIONAL, 'Name of the controller.', 'UsersController'), + array('--restful', '-r', InputOption::VALUE_NONE, 'Generate RESTful controller.'), + ); } /** @@ -39,120 +47,52 @@ public function __construct() */ public function fire() { - $name = $this->prepareName($this->option('controller')); + // Prepare variables + $controllerName = $this->option('controller'); $restful = $this->option('restful'); + $url = 'users'; + + $viewVars = compact( + 'controllerName', + 'url', + 'restful' + ); + + // Prompt $this->line(''); $this->info( "Routes file: app/routes.php" ); - if(! $restful) - { - $message = "The default Confide routes (to use with the Controller template)". - " will be appended to your routes.php file."; - } - else - { - $message = "A single route to handle every action in a RESTful controller". - " will be appended to your routes.php file. This may be used with a confide". - " controller generated using [-r|--restful] option."; - } - + $message = $this->getFireMessage($restful); - $this->comment( $message ); + $this->comment($message); $this->line(''); if ( $this->confirm("Proceed with the append? [Yes|no]") ) { - $this->line(''); - $this->info( "Appending routes..." ); - if( $this->appendRoutes( $name, $restful ) ) - { - $this->info( "app/routes.php Patched successfully!" ); - } - else{ - $this->error( - "Coudn't append content to app/routes.php\nCheck the". - " write permissions within the file." - ); - } - - $this->line(''); + // Generate + $filename = 'routes.php'; + $this->appendInFile($filename, 'generators.routes', $viewVars); + $this->info("app/routes.php Patched successfully!"); } } /** - * Get the console command options. - * - * @return array - */ - protected function getOptions() - { - $app = app(); - - return array( - array('controller', null, InputOption::VALUE_OPTIONAL, 'Name of the controller.', $app['config']->get('auth.model')), - array('--restful', '-r', InputOption::VALUE_NONE, 'Generate RESTful controller.'), - ); - } - - /** - * Prepare the controller name - * - * @param string $name - * @return string + * Returns a message that should explain what is about to be done. + * @param boolean $restful If the restful option is being used + * @return string The message */ - protected function prepareName( $name = '' ) + protected function getFireMessage($restful = false) { - $name = ( $name != '') ? ucfirst($name) : 'User'; - - if( substr($name,-10) == 'controller' ) - { - $name = substr($name, 0, -10).'Controller'; - } - else - { - $name .= 'Controller'; - } - - return $name; - } - - /** - * Create the controller - * - * @param string $name - * @return bool - */ - protected function appendRoutes( $name = '', $restful = false ) - { - $app = app(); - $routes_file = $this->laravel->path.'/routes.php'; - $confide_routes = $app['view']->make('confide::generators.routes') - ->with('name', $name) - ->with('restful', $restful) - ->render(); - - if( file_exists( $routes_file ) ) - { - $fs = fopen($routes_file, 'a'); - if ( $fs ) - { - fwrite($fs, $confide_routes); - $this->line($confide_routes); - fclose($fs); - return true; - } - else - { - return false; - } - } - else - { - return false; + if(! $restful) { + return "The default Confide routes (to use with the Controller template)". + " will be appended to your routes.php file."; + } else { + return "A single route to handle every action in a RESTful controller". + " will be appended to your routes.php file. This may be used with a confide". + " controller generated using [-r|--restful] option."; } } - } diff --git a/src/config/config.php b/src/config/config.php index 8a6fc61..c21d319 100644 --- a/src/config/config.php +++ b/src/config/config.php @@ -75,20 +75,16 @@ /* |-------------------------------------------------------------------------- - | Signup (create) Cache + | Password reset expiration |-------------------------------------------------------------------------- | - | By default you will only can only register once every 2 hours - | (120 minutes) because you are not able to receive a registration - | email more often then that. - | - | You can adjust that limitation here, set to 0 for no caching. - | Time is in minutes. - | + | By default. A password reset request will expire after 7 hours. With the + | line below you will be able to customize the duration of the reset + | requests here. | */ - 'signup_cache' => 120, - + 'password_reset_expiration' => 7, // hours + /* |-------------------------------------------------------------------------- | Signup E-mail and confirmation (true or false) @@ -106,7 +102,7 @@ | signup_confirm: | is to decide of a member needs to be confirmed before he is able to login | so when you set this to true, then a member has to be confirmed before - | he is able to login, so if you want to use an IPN for confirmation, be + | he is able to login, so if you want to use an IPN for confirmation, be | sure that the ipn process also changes the confirmed flag in the member | table, otherwise they will not be able to login after the payment. | diff --git a/src/lang/nl/confide.php b/src/lang/nl/confide.php index 5be0816..c239806 100644 --- a/src/lang/nl/confide.php +++ b/src/lang/nl/confide.php @@ -1,8 +1,7 @@ 'Gebruikersnaam', - 'password' => 'Wachtwoord', - 'password_confirmation' => 'Bevestig wachtwoord', - 'e_mail' => 'E-mail', - 'username_e_mail' => 'Gebruikersnaam of e-mailadres', - - 'signup' => array( - 'title' => 'Registreren', - 'desc' => 'Registreer een nieuw account', - 'confirmation_required' => 'Bevestiging vereist', - 'submit' => 'Maak nieuw account', - ), - - 'login' => array( - 'title' => 'Inloggen', - 'desc' => 'Vul je gegevens in', - 'forgot_password' => '(wachtwoord vergeten)', - 'remember' => 'Onthoud mij', - 'submit' => 'Inloggen', - ), - - 'forgot' => array( - 'title' => 'Wachtwoord vergeten', - 'submit' => 'Doorgaan', - ), - - 'alerts' => array( - 'account_created' => 'Je account is succesvol aangemaakt. Controleer je e-mailinbox voor instructies om je account the bevestigen.', - 'too_many_attempts' => 'Te veel pogingen. Probeer het over een enkele minuten nog eens.', - 'wrong_credentials' => 'Verkeerde gebruikersnaam, e-mailadres of wachtwoord.', - 'not_confirmed' => 'Je account is waarschijnlijk niet bevestigd. Controleer je e-mailinbox voor de bevestigingslink.', - 'confirmation' => 'Je account is bevestigd! Je kunt nu inloggen.', - 'wrong_confirmation' => 'Verkeerde bevestigingscode.', - 'password_forgot' => 'De informatie voor het opnieuw instellen van je wachtwoord is verstuurd naar je e-mailadres.', - 'wrong_password_forgot' => 'Gebruiker niet gevonden.', - 'password_reset' => 'Je wachtwoord is succesvol veranderd.', - 'wrong_password_reset' => 'Verkeerd wachtwoord. Probeer het nog eens.', - 'wrong_token' => 'Het token om je wachtwoord opnieuw in te stellen is niet geldig.', - 'duplicated_credentials' => 'De ingevulde gegevens zijn al in gebruik. Probeer het eens met andere gegevens.', - ), - - 'email' => array( - 'account_confirmation' => array( - 'subject' => 'Accountbevestiging', - 'greetings' => 'Hallo :name', - 'body' => 'Klik op de onderstaande link om je account te bevestigen.', - 'farewell' => 'Met vriendelijke groet', - ), - - 'password_reset' => array( - 'subject' => 'Wachtwoord opnieuw instellen', - 'greetings' => 'Hallo :name', - 'body' => 'Klik op de onderstaande link om je wachtwoord opnieuw in te stellen.', - 'farewell' => 'Met vriendelijke groet', - ), - ), - -); -*/ diff --git a/src/lang/nl_NL/confide.php b/src/lang/nl_NL/confide.php new file mode 100644 index 0000000..a7b70af --- /dev/null +++ b/src/lang/nl_NL/confide.php @@ -0,0 +1,66 @@ + 'Gebruikersnaam', + 'password' => 'Wachtwoord', + 'password_confirmation' => 'Bevestig wachtwoord', + 'e_mail' => 'E-mail', + 'username_e_mail' => 'Gebruikersnaam of e-mailadres', + + 'signup' => array( + 'title' => 'Registreren', + 'desc' => 'Registreer een nieuw account', + 'confirmation_required' => 'Bevestiging vereist', + 'submit' => 'Maak nieuw account', + ), + + 'login' => array( + 'title' => 'Inloggen', + 'desc' => 'Vul je gegevens in', + 'forgot_password' => '(wachtwoord vergeten)', + 'remember' => 'Onthoud mij', + 'submit' => 'Inloggen', + ), + + 'forgot' => array( + 'title' => 'Wachtwoord vergeten', + 'submit' => 'Doorgaan', + ), + + 'alerts' => array( + 'account_created' => 'Je account is succesvol aangemaakt. Controleer je e-mailinbox voor instructies om je account the bevestigen.', + 'too_many_attempts' => 'Te veel pogingen. Probeer het over een enkele minuten nog eens.', + 'wrong_credentials' => 'Verkeerde gebruikersnaam, e-mailadres of wachtwoord.', + 'not_confirmed' => 'Je account is waarschijnlijk niet bevestigd. Controleer je e-mailinbox voor de bevestigingslink.', + 'confirmation' => 'Je account is bevestigd! Je kunt nu inloggen.', + 'wrong_confirmation' => 'Verkeerde bevestigingscode.', + 'password_forgot' => 'De informatie voor het opnieuw instellen van je wachtwoord is verstuurd naar je e-mailadres.', + 'wrong_password_forgot' => 'Gebruiker niet gevonden.', + 'password_reset' => 'Je wachtwoord is succesvol veranderd.', + 'wrong_password_reset' => 'Verkeerd wachtwoord. Probeer het nog eens.', + 'wrong_token' => 'Het token om je wachtwoord opnieuw in te stellen is niet geldig.', + 'duplicated_credentials' => 'De ingevulde gegevens zijn al in gebruik. Probeer het eens met andere gegevens.', + ), + + 'email' => array( + 'account_confirmation' => array( + 'subject' => 'Accountbevestiging', + 'greetings' => 'Hallo :name', + 'body' => 'Klik op de onderstaande link om je account te bevestigen.', + 'farewell' => 'Met vriendelijke groet', + ), + + 'password_reset' => array( + 'subject' => 'Wachtwoord opnieuw instellen', + 'greetings' => 'Hallo :name', + 'body' => 'Klik op de onderstaande link om je wachtwoord opnieuw in te stellen.', + 'farewell' => 'Met vriendelijke groet', + ), + ), + +); diff --git a/src/migrations/2013_01_13_172956_confide_setup_users_table.php b/src/migrations/2013_01_13_172956_confide_setup_users_table.php index 0242339..40df062 100644 --- a/src/migrations/2013_01_13_172956_confide_setup_users_table.php +++ b/src/migrations/2013_01_13_172956_confide_setup_users_table.php @@ -12,17 +12,25 @@ class ConfideSetupUsersTable extends Migration { public function up() { // Creates the users table - Schema::create('users', function($table) - { - $table->increments('id'); - $table->string('username'); - $table->string('email'); - $table->string('password'); - $table->string('confirmation_code'); - $table->boolean('confirmed')->default(false); - $table->string('remember_token')->nullable(); - $table->timestamps(); - }); + Schema::create('users', function($table) + { + $table->increments('id'); + $table->string('username')->unique(); + $table->string('email')->unique(); + $table->string('password'); + $table->string('confirmation_code'); + $table->string('remember_token'); + $table->boolean('confirmed')->default(false); + $table->timestamps(); + }); + + // Creates password reminders table + Schema::create('password_reminders', function($t) + { + $t->string('email'); + $t->string('token'); + $t->timestamp('created_at'); + }); } /** @@ -32,6 +40,7 @@ public function up() */ public function down() { + Schema::drop('password_reminders'); Schema::drop('users'); } diff --git a/src/views/emails/confirm.blade.php b/src/views/emails/confirm.blade.php index a939f0c..847d630 100644 --- a/src/views/emails/confirm.blade.php +++ b/src/views/emails/confirm.blade.php @@ -3,8 +3,8 @@

{{ Lang::get('confide::confide.email.account_confirmation.greetings', array( 'name' => $user->username)) }},

{{ Lang::get('confide::confide.email.account_confirmation.body') }}

- - {{{ URL::to("user/confirm/{$user->confirmation_code}") }}} + + {{{ URL::to("users/confirm/{$user->confirmation_code}") }}}

{{ Lang::get('confide::confide.email.account_confirmation.farewell') }}

diff --git a/src/views/emails/passwordreset.blade.php b/src/views/emails/passwordreset.blade.php index ccb7b8e..2632bb0 100644 --- a/src/views/emails/passwordreset.blade.php +++ b/src/views/emails/passwordreset.blade.php @@ -3,8 +3,8 @@

{{ Lang::get('confide::confide.email.password_reset.greetings', array( 'name' => $user->username)) }},

{{ Lang::get('confide::confide.email.password_reset.body') }}

- - {{{ (Confide::checkAction('UserController@reset_password', array($token))) ? : URL::to('user/reset/'.$token) }}} + + {{ URL::to('users/reset_password/'.$token) }}

{{ Lang::get('confide::confide.email.password_reset.farewell') }}

diff --git a/src/views/forgot_password.blade.php b/src/views/forgot_password.blade.php index 06d20f6..4b75357 100644 --- a/src/views/forgot_password.blade.php +++ b/src/views/forgot_password.blade.php @@ -1,4 +1,4 @@ -
+
diff --git a/src/views/generators/controller.blade.php b/src/views/generators/controller.blade.php index f5f8d83..4108c29 100644 --- a/src/views/generators/controller.blade.php +++ b/src/views/generators/controller.blade.php @@ -1,20 +1,22 @@ - +{{ $namespace ? ' namespace '.$namespace.';' : '' }} -/* -|-------------------------------------------------------------------------- -| Confide Controller Template -|-------------------------------------------------------------------------- -| -| This is the default Confide controller template for controlling user -| authentication. Feel free to change to your needs. -| -*/ + +@if ($namespace) -class {{ $name }} extends BaseController { +use App, View, Input, Config, Redirect, Lang, Mail, Confide; +use Controller; +@endif + +/** + * UsersController Class + * + * Implements actions regarding user management + */ +class {{ $class }} extends Controller { /** * Displays the form for account creation - * + * @return Illuminate\Http\Response */ public function {{ (! $restful) ? 'create' : 'getCreate' }}() { @@ -23,50 +25,29 @@ public function {{ (! $restful) ? 'create' : 'getCreate' }}() /** * Stores new account - * + * @return Illuminate\Http\Response */ public function {{ (! $restful) ? 'store' : 'postIndex' }}() { - ${{ lcfirst(Config::get('auth.model')) }} = new {{ Config::get('auth.model') }}; - - ${{ lcfirst(Config::get('auth.model')) }}->username = Input::get( 'username' ); - ${{ lcfirst(Config::get('auth.model')) }}->email = Input::get( 'email' ); - ${{ lcfirst(Config::get('auth.model')) }}->password = Input::get( 'password' ); - - // The password confirmation will be removed from model - // before saving. This field will be used in Ardent's - // auto validation. - ${{ lcfirst(Config::get('auth.model')) }}->password_confirmation = Input::get( 'password_confirmation' ); - - // Save if valid. Password field will be hashed before save - ${{ lcfirst(Config::get('auth.model')) }}->save(); + $repo = App::make('{{ $repositoryClass }}'); + $user = $repo->signup(Input::all()); - if ( ${{ lcfirst(Config::get('auth.model')) }}->getKey() ) + if ($user->id) { - @if ( Config::get('confide::signup_confirm') && Config::get('confide::signup_email')) - $notice = Lang::get('confide::confide.alerts.account_created') . ' ' . Lang::get('confide::confide.alerts.instructions_sent'); - @else - $notice = Lang::get('confide::confide.alerts.account_created'); - @endif - - // Redirect with success message, You may replace "Lang::get(..." for your custom message. - @if (! $restful) - return Redirect::action('{{ $name }}@login') - @else - return Redirect::to('user/login') - @endif - ->with( 'notice', $notice ); + Mail::send(Config::get('confide::email_account_confirmation'), compact('user'), function($message) use ($user) { + $message + ->to($user->email, $user->username) + ->subject(Lang::get('confide::confide.email.account_confirmation.subject')); + }); + + return Redirect::action('{{ $namespace ? $namespace.'\\' : '' }}{{ $class }}@login') + ->with( 'notice', Lang::get('confide::confide.alerts.account_created') ); } else { - // Get validation errors (see Ardent package) - $error = ${{ lcfirst(Config::get('auth.model')) }}->errors()->all(':message'); + $error = $user->errors()->all(':message'); - @if (! $restful) - return Redirect::action('{{ $name }}@create') - @else - return Redirect::to('user/create') - @endif + return Redirect::action('{{ $namespace ? $namespace.'\\' : '' }}{{ $class }}@create') ->withInput(Input::except('password')) ->with( 'error', $error ); } @@ -74,14 +55,12 @@ public function {{ (! $restful) ? 'store' : 'postIndex' }}() /** * Displays the login form - * + * @return Illuminate\Http\Response */ public function {{ (! $restful) ? 'login' : 'getLogin' }}() { if( Confide::user() ) { - // If user is logged, redirect to internal - // page, change it to '/admin', '/dashboard' or something return Redirect::to('/'); } else @@ -92,39 +71,24 @@ public function {{ (! $restful) ? 'login' : 'getLogin' }}() /** * Attempt to do login - * + * @return Illuminate\Http\Response */ public function {{ (! $restful) ? 'do_login' : 'postLogin' }}() { - $input = array( - 'email' => Input::get( 'email' ), // May be the username too - 'username' => Input::get( 'email' ), // so we have to pass both - 'password' => Input::get( 'password' ), - 'remember' => Input::get( 'remember' ), - ); + $repo = App::make('{{ $repositoryClass }}'); + $input = Input::all(); - // If you wish to only allow login from confirmed users, call logAttempt - // with the second parameter as true. - // logAttempt will check if the 'email' perhaps is the username. - // Get the value from the config file instead of changing the controller - if ( Confide::logAttempt( $input, Config::get('confide::signup_confirm') ) ) + if ($repo->login($input)) { - // Redirect the user to the URL they were trying to access before - // caught by the authentication filter IE Redirect::guest('user/login'). - // Otherwise fallback to '/' - // Fix pull #145 - return Redirect::intended('/'); // change it to '/admin', '/dashboard' or something + return Redirect::intended('/'); } else { - ${{ lcfirst(Config::get('auth.model')) }} = new {{ Config::get('auth.model') }}; - - // Check if there was too many login attempts - if( Confide::isThrottled( $input ) ) + if ($repo->isThrottled($input)) { $err_msg = Lang::get('confide::confide.alerts.too_many_attempts'); } - elseif( ${{ lcfirst(Config::get('auth.model')) }}->checkUserExists( $input ) and ! ${{ lcfirst(Config::get('auth.model')) }}->isConfirmed( $input ) ) + elseif ($repo->existsButNotConfirmed($input)) { $err_msg = Lang::get('confide::confide.alerts.not_confirmed'); } @@ -133,11 +97,7 @@ public function {{ (! $restful) ? 'do_login' : 'postLogin' }}() $err_msg = Lang::get('confide::confide.alerts.wrong_credentials'); } - @if (! $restful) - return Redirect::action('{{ $name }}@login') - @else - return Redirect::to('user/login') - @endif + return Redirect::action('{{ $namespace ? $namespace.'\\' : '' }}{{ $class }}@login') ->withInput(Input::except('password')) ->with( 'error', $err_msg ); } @@ -145,36 +105,28 @@ public function {{ (! $restful) ? 'do_login' : 'postLogin' }}() /** * Attempt to confirm account with code - * * @param string $code + * @return Illuminate\Http\Response */ public function {{ (! $restful) ? 'confirm' : 'getConfirm' }}( $code ) { if ( Confide::confirm( $code ) ) { $notice_msg = Lang::get('confide::confide.alerts.confirmation'); - @if (! $restful) - return Redirect::action('{{ $name }}@login') - @else - return Redirect::to('user/login') - @endif + return Redirect::action('{{ $namespace ? $namespace.'\\' : '' }}{{ $class }}@login') ->with( 'notice', $notice_msg ); } else { $error_msg = Lang::get('confide::confide.alerts.wrong_confirmation'); - @if (! $restful) - return Redirect::action('{{ $name }}@login') - @else - return Redirect::to('user/login') - @endif + return Redirect::action('{{ $namespace ? $namespace.'\\' : '' }}{{ $class }}@login') ->with( 'error', $error_msg ); } } /** * Displays the forgot password form - * + * @return Illuminate\Http\Response */ public function {{ (! $restful) ? 'forgot_password' : 'getForgot' }}() { @@ -183,28 +135,20 @@ public function {{ (! $restful) ? 'forgot_password' : 'getForgot' }}() /** * Attempt to send change password link to the given email - * + * @return Illuminate\Http\Response */ public function {{ (! $restful) ? 'do_forgot_password' : 'postForgot' }}() { if( Confide::forgotPassword( Input::get( 'email' ) ) ) { $notice_msg = Lang::get('confide::confide.alerts.password_forgot'); - @if (! $restful) - return Redirect::action('{{ $name }}@login') - @else - return Redirect::to('user/login') - @endif + return Redirect::action('{{ $namespace ? $namespace.'\\' : '' }}{{ $class }}@login') ->with( 'notice', $notice_msg ); } else { $error_msg = Lang::get('confide::confide.alerts.wrong_password_forgot'); - @if (! $restful) - return Redirect::action('{{ $name }}@forgot_password') - @else - return Redirect::to('user/forgot') - @endif + return Redirect::action('{{ $namespace ? $namespace.'\\' : '' }}{{ $class }}@forgot_password') ->withInput() ->with( 'error', $error_msg ); } @@ -212,7 +156,7 @@ public function {{ (! $restful) ? 'do_forgot_password' : 'postForgot' }}() /** * Shows the change password form with the given token - * + * @return Illuminate\Http\Response */ public function {{ (! $restful) ? 'reset_password' : 'getReset' }}( $token ) { @@ -222,35 +166,28 @@ public function {{ (! $restful) ? 'reset_password' : 'getReset' }}( $token ) /** * Attempt change password of the user - * + * @return Illuminate\Http\Response */ public function {{ (! $restful) ? 'do_reset_password' : 'postReset' }}() { + $repo = App::make('{{ $repositoryClass }}'); $input = array( - 'token'=>Input::get( 'token' ), - 'password'=>Input::get( 'password' ), - 'password_confirmation'=>Input::get( 'password_confirmation' ), + 'token' =>Input::get( 'token' ), + 'password' =>Input::get( 'password' ), + 'password_confirmation' =>Input::get( 'password_confirmation' ), ); // By passing an array with the token, password and confirmation - if( Confide::resetPassword( $input ) ) + if( $repo->resetPassword( $input ) ) { $notice_msg = Lang::get('confide::confide.alerts.password_reset'); - @if (! $restful) - return Redirect::action('{{ $name }}@login') - @else - return Redirect::to('user/login') - @endif + return Redirect::action('{{ $namespace ? $namespace.'\\' : '' }}{{ $class }}@login') ->with( 'notice', $notice_msg ); } else { $error_msg = Lang::get('confide::confide.alerts.wrong_password_reset'); - @if (! $restful) - return Redirect::action('{{ $name }}@reset_password', array('token'=>$input['token'])) - @else - return Redirect::to('user/reset/'.$input['token']) - @endif + return Redirect::action('{{ $namespace ? $namespace.'\\' : '' }}{{ $class }}@reset_password', array('token'=>$input['token'])) ->withInput() ->with( 'error', $error_msg ); } @@ -258,7 +195,7 @@ public function {{ (! $restful) ? 'do_reset_password' : 'postReset' }}() /** * Log the user out of the application. - * + * @return Illuminate\Http\Response */ public function {{ (! $restful) ? 'logout' : 'getLogout' }}() { diff --git a/src/views/generators/migration.blade.php b/src/views/generators/migration.blade.php index a4ddd0c..1fa6071 100644 --- a/src/views/generators/migration.blade.php +++ b/src/views/generators/migration.blade.php @@ -15,10 +15,11 @@ public function up() Schema::create('{{ $table }}', function($table) { $table->increments('id'); - $table->string('username'); - $table->string('email'); + $table->string('username')->unique(); + $table->string('email')->unique(); $table->string('password'); $table->string('confirmation_code'); + $table->string('remember_token'); $table->boolean('confirmed')->default(false); $table->timestamps(); }); diff --git a/src/views/generators/repository.blade.php b/src/views/generators/repository.blade.php new file mode 100644 index 0000000..de34ecb --- /dev/null +++ b/src/views/generators/repository.blade.php @@ -0,0 +1,117 @@ +{{ strstr($model, '\\') ? ' namespace '.substr($model, 0, -strlen(strrchr($model, '\\'))).';' : '' }} + + +@if (strstr($model, '\\')) + +use App, Config, Confide; +@endif + +/** + * Class UserRepository + * + * This service abstracts some interactions that occurs between Confide and + * the Database. + */ +class UserRepository +{ + /** + * Signup a new account with the given parameters + * @param array $input Array containing 'username', 'email' and 'password'. + * @return {{ $nonNamespacedName }} {{ $nonNamespacedName }} object that may or may not be saved successfully. Check the id to make sure. + */ + public function signup($input) + { + $user = new {{ $nonNamespacedName }}; + + $user->username = array_get($input, 'username'); + $user->email = array_get($input, 'email'); + $user->password = array_get($input, 'password'); + + // The password confirmation will be removed from model + // before saving. This field will be used in Ardent's + // auto validation. + $user->password_confirmation = array_get($input, 'password_confirmation' ); + + // Generate a random confirmation code + $user->confirmation_code = md5($user->username.time('U')); + + // Save if valid. Password field will be hashed before save + $this->save($user); + + return $user; + } + + /** + * Attempts to login with the given credentials. + * @param array $input Array containing the credentials (email/username and password) + * @return boolean Success? + */ + public function login($input) + { + if(! isset($input['password'])) + $input['password'] = null; + + $identityColumns = ['email', 'username']; + + return Confide::logAttempt($input, Config::get('confide::signup_confirm'), $identityColumns); + } + + /** + * Checks if the credentials has been throttled by too + * much failed login attempts + * + * @param array $credentials Array containing the credentials (email/username and password) + * @return boolean Is throttled + */ + public function isThrottled($input) + { + return Confide::isThrottled($input); + } + + /** + * Checks if the given credentials correponds to a user that exists but + * is not confirmed + * @param array $credentials Array containing the credentials (email/username and password) + * @return boolean Exists and is not confirmed? + */ + public function existsButNotConfirmed($input) + { + $user = Confide::getUserByEmailOrUsername($input); + + if ($user) + return ! $user->confirmed; + } + + /** + * Resets a password of a user. The $input['token'] will tell which user. + * @param array $input Array containing 'token', 'password' and 'password_confirmation' keys. + * @return boolean Success + */ + public function resetPassword($input) + { + $result = false; + $user = Confide::userByResetPasswordToken($input['token']); + + if ($user) { + $user->password = $input['password']; + $user->password_confirmation = $input['password_confirmation']; + $result = $this->save($user); + } + + // If result is positive, destroy token + if ($result) + Confide::destroyForgotPasswordToken($input['token']); + + return $result; + } + + /** + * Simply saves the given instance + * @param {{ $nonNamespacedName }} $instance + * @return boolean Success + */ + public function save({{ $nonNamespacedName }} $instance) + { + return $instance->save(); + } +} diff --git a/src/views/generators/routes.blade.php b/src/views/generators/routes.blade.php index 7d76b3d..fa01117 100644 --- a/src/views/generators/routes.blade.php +++ b/src/views/generators/routes.blade.php @@ -1,21 +1,22 @@ -{{ "\n\n" }} +// @if (! $restful) // Confide routes -Route::get( '{{ lcfirst(substr($name,0,-10)) }}/create', '{{ $name }}@create'); -Route::post('{{ lcfirst(substr($name,0,-10)) }}', '{{ $name }}@store'); -Route::get( '{{ lcfirst(substr($name,0,-10)) }}/login', '{{ $name }}@login'); -Route::post('{{ lcfirst(substr($name,0,-10)) }}/login', '{{ $name }}@do_login'); -Route::get( '{{ lcfirst(substr($name,0,-10)) }}/confirm/{code}', '{{ $name }}@confirm'); -Route::get( '{{ lcfirst(substr($name,0,-10)) }}/forgot_password', '{{ $name }}@forgot_password'); -Route::post('{{ lcfirst(substr($name,0,-10)) }}/forgot_password', '{{ $name }}@do_forgot_password'); -Route::get( '{{ lcfirst(substr($name,0,-10)) }}/reset_password/{token}', '{{ $name }}@reset_password'); -Route::post('{{ lcfirst(substr($name,0,-10)) }}/reset_password', '{{ $name }}@do_reset_password'); -Route::get( '{{ lcfirst(substr($name,0,-10)) }}/logout', '{{ $name }}@logout'); +Route::get( '{{ $url }}/create', '{{ $controllerName }}@create'); +Route::post('{{ $url }}', '{{ $controllerName }}@store'); +Route::get( '{{ $url }}/login', '{{ $controllerName }}@login'); +Route::post('{{ $url }}/login', '{{ $controllerName }}@do_login'); +Route::get( '{{ $url }}/confirm/{code}', '{{ $controllerName }}@confirm'); +Route::get( '{{ $url }}/forgot_password', '{{ $controllerName }}@forgot_password'); +Route::post('{{ $url }}/forgot_password', '{{ $controllerName }}@do_forgot_password'); +Route::get( '{{ $url }}/reset_password/{token}', '{{ $controllerName }}@reset_password'); +Route::post('{{ $url }}/reset_password', '{{ $controllerName }}@do_reset_password'); +Route::get( '{{ $url }}/logout', '{{ $controllerName }}@logout'); @else // Confide RESTful route -Route::get('user/confirm/{code}', '{{ $name }}@getConfirm'); -Route::get('user/reset/{token}', '{{ $name }}@getReset'); -Route::controller( 'user', '{{ $name }}'); +Route::get('{{ $url }}/confirm/{code}', '{{ $controllerName }}@getConfirm'); +Route::get('{{ $url }}/reset_password/{token}', '{{ $controllerName }}@getReset'); +Route::get('{{ $url }}/reset_password', '{{ $controllerName }}@postReset'); +Route::controller( '{{ $url }}', '{{ $controllerName }}'); @endif diff --git a/src/views/login.blade.php b/src/views/login.blade.php index 4d989b7..c3d194e 100644 --- a/src/views/login.blade.php +++ b/src/views/login.blade.php @@ -1,4 +1,4 @@ - +
@@ -9,7 +9,7 @@ diff --git a/src/views/reset_password.blade.php b/src/views/reset_password.blade.php index 7198159..587cf87 100644 --- a/src/views/reset_password.blade.php +++ b/src/views/reset_password.blade.php @@ -1,4 +1,4 @@ - + diff --git a/src/views/signup.blade.php b/src/views/signup.blade.php index afe024c..c273184 100644 --- a/src/views/signup.blade.php +++ b/src/views/signup.blade.php @@ -1,4 +1,4 @@ - +
diff --git a/tests/ConfideEloquentRepositoryTest.php b/tests/ConfideEloquentRepositoryTest.php deleted file mode 100644 index 9de14d2..0000000 --- a/tests/ConfideEloquentRepositoryTest.php +++ /dev/null @@ -1,530 +0,0 @@ -mockApp(); - $this->repo = new ConfideEloquentRepository(); - - // Set the app attribute with mock - $this->repo->app = $app; - } - - public function tearDown() - { - m::close(); - } - - public function testGetModel() - { - // Make sure to return the wanted value from config - $config = m::mock('Illuminate\Config\Repository'); - $config->shouldReceive('get') - ->with('auth.model') - ->andReturn( '_mockedUser' ) - ->once(); - $this->repo->app['config'] = $config; - - // Mocks an user - $confide_user = $this->mockConfideUser(); - - // Runs the `model()` method - $user = $this->repo->model(); - - // Assert the result - $this->assertInstanceOf('_mockedUser', $user); - } - - public function testShouldConfirm() - { - // Make sure that our user will recieve confirm - $confide_user = m::mock(new _mockedUser); - $confide_user->shouldReceive('confirm') // Should receive confirm - ->andReturn( true ) - ->once() - - ->getMock()->shouldReceive('where') // Should query for the model - ->with('confirmation_code', '=', '123123') - ->andReturn( $confide_user ) - ->once() - - ->getMock()->shouldReceive('get') - ->andReturn( $confide_user ) - ->once() - - ->getMock()->shouldReceive('first') - ->andReturn( $confide_user ) - ->once(); - - // This will make sure that the mocked user will be returned - // when calling `model()` (that will occur inside `repo->confirm()`) - $this->repo->model = $confide_user; - - $this->assertTrue( $this->repo->confirm( '123123' ) ); - } - - public function testShouldGetByEmail() - { - // Make sure that our user will recieve confirm - $confide_user = m::mock(new _mockedUser); - $confide_user->shouldReceive('where') // Should query for the model - ->with('email', '=', 'lol@sample.com') - ->andReturn( $confide_user ) - ->once() - - ->getMock()->shouldReceive('get') - ->andReturn( $confide_user ) - ->once() - - ->getMock()->shouldReceive('first') - ->andReturn( $confide_user ) - ->once(); - - // This will make sure that the mocked user will be returned - // when calling `model()` (that will occur inside `repo->confirm()`) - $this->repo->model = $confide_user; - - $this->assertEquals( $confide_user, $this->repo->getUserByMail( 'lol@sample.com' ) ); - } - - public function testShouldGetByIdentity() - { - // Make sure that our user will be returned when querying - $confide_user = m::mock(new _mockedUser); - - $confide_user->email = 'lol@sample.com'; - $confide_user->username = 'LoL'; - - $confide_user->shouldReceive('where') // Should query for the model - ->with('email', 'lol@sample.com') - ->andReturn( $confide_user ) - ->atLeast(1) - - ->getMock()->shouldReceive('where') - ->with('username', 'LoL') - ->andReturn( $confide_user ) - ->once() - - ->getMock()->shouldReceive('orWhere') - ->with('username', 'LoL') - ->andReturn( $confide_user ) - ->once() - - ->getMock()->shouldReceive('get') - ->andReturn( $confide_user ) - ->atLeast(1) - - ->getMock()->shouldReceive('first') - ->andReturn( $confide_user ) - ->atLeast(1); - - // This will make sure that the mocked user will be returned - // when calling `model()` (that will occur inside `repo->confirm()`) - $this->repo->model = $confide_user; - - // Parameters to search for - $values = array( - 'email' => 'lol@sample.com', - 'username' => 'LoL', - ); - - // Identity - $identity = array( 'email','username' ); - - // Using array - $this->assertEquals( - $confide_user, $this->repo->getUserByIdentity( $values, $identity ) - ); - - // Using string - $this->assertEquals( - $confide_user, $this->repo->getUserByIdentity( $values, 'email' ) - ); - - // Using string for username - $this->assertEquals( - $confide_user, $this->repo->getUserByIdentity( $values, 'username' ) - ); - - // When passing empty credentials should return null - $this->assertEquals( - null, $this->repo->getUserByIdentity( array() ) - ); - - // When passing credentials that don't exist should return null - $this->assertEquals( - null, $this->repo->getUserByIdentity( array('token' => 'random-token-value') ) - ); - } - - public function testShouldGetPasswordRemindersCountByToken() - { - // Make sure that our user will recieve confirm - $database = m::mock('DatabaseManager'); - $database->shouldReceive('connection') // Should query for the password reminders with the given token - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('table') - ->with('password_reminders') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('where') - ->with('token', '=', '456456') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('count') - ->andReturn( 1 ) - ->once(); - - $this->repo->app['db'] = $database; - - $this->assertEquals( 1, $this->repo->getPasswordRemindersCount( '456456' ) ); - } - - public function testShouldGetPasswordReminderEmailByToken() - { - // Make sure that our user will recieve confirm - $database = m::mock('DatabaseManager'); - $database->shouldReceive('connection') // Should query for the password reminders with the given token - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('table') - ->with('password_reminders') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('select') - ->with('email') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('where') - ->with('token', '=', '456456') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('first') - ->andReturn('lol@sample.com') - ->once(); - - $this->repo->app['db'] = $database; - - $this->assertEquals( 'lol@sample.com', $this->repo->getEmailByReminderToken( '456456' ) ); - } - - public function testShouldDeletePasswordReminderEmailByToken() - { - // Make sure that our user will recieve confirm - $database = m::mock('DatabaseManager'); - $database->shouldReceive('connection') // Should query for the password reminders with the given token - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('table') - ->with('password_reminders') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('select') - ->with('email') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('where') - ->with('token', '=', '456456') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('delete') - ->once(); - - $this->repo->app['db'] = $database; - - $this->assertNull( $this->repo->deleteEmailByReminderToken( '456456' ) ); - } - - public function testShouldChangePassword() - { - // Make sure that the mock will return the table name - $confide_user = m::mock(new _mockedUser); - $confide_user->shouldReceive('getTable') - ->andReturn( 'users' ) - ->once() - - ->getMock()->shouldReceive('getKey') - ->andReturn( '3' ) - ->once() - - ->getMock()->shouldReceive('getKeyName') - ->andReturn( 'id' ) - ->once(); - - // Mocks DB in order to check for the following query: - // DB::table('users')->where('id',3)->update(array('password'=>'lol')); - $database = m::mock('DatabaseManager'); - $database->shouldReceive('connection') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('table') - ->with( 'users' ) - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('where') - ->with('id','3') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('update') - ->with(array('password'=>'secret')) - ->andReturn( 0 ) - ->once(); - - $this->repo->app['db'] = $database; - - // Actually change the user password - $this->assertTrue( - $this->repo->changePassword($confide_user, 'secret') - ); - } - - public function testShouldForgotPassword() - { - // Make sure that the mock will return the table name - $confide_user = m::mock(new _mockedUser); - - $confide_user->email = 'bob@sample.com'; - - $timeStamp = new \DateTime; - - // Mocks DB in order to check for the following query: - // DB::table('password_reminders')->insert(array( - // 'email'=> $this->email, - // 'token'=> $token, - // 'created_at'=> new \DateTime - //)); - $database = m::mock('DatabaseManager'); - $database->shouldReceive('connection') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('table') - ->with( 'password_reminders' ) - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('insert') - ->andReturn( true ) - ->once(); - - $this->repo->app['db'] = $database; - - // Actually checks if the token is returned - $this->assertNotNull( - $this->repo->forgotPassword($confide_user) - ); - } - - public function testUserExists() - { - // Make sure that the mock will return the table name - $confide_user = m::mock(new _mockedUser); - - $confide_user->username = 'Bob'; - $confide_user->email = 'bob@sample.com'; - - $confide_user->shouldReceive('getTable') - ->andReturn( 'users' ) - ->once(); - - // Mocks DB in order to check for the following query: - // DB::table('users')->where('email', 'bob@sample.com')->orWhere('username', 'bob')->count(); - $database = m::mock('DatabaseManager'); - $database->shouldReceive('connection') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('table') - ->with( 'users' ) - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('where') - ->with('email', 'bob@sample.com') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('orWhere') - ->with('username', 'Bob') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('count') - ->andReturn( 1 ) - ->once(); - - $this->repo->app['db'] = $database; - - // Actually checks if the user exists - $this->assertEquals( - 1, - $this->repo->userExists($confide_user) - ); - } - - public function testShouldConfirmUser() - { - // Make sure that the mock will return the table name - $confide_user = m::mock(new _mockedUser); - $confide_user->shouldReceive('getTable') - ->andReturn( 'users' ) - ->once() - - ->getMock()->shouldReceive('getKey') - ->andReturn( '3' ) - ->once() - - ->getMock()->shouldReceive('getKeyName') - ->andReturn( 'id' ) - ->once(); - - // Mocks DB in order to check for the following query: - // DB::table('users')->where('id',3)->update(array('confirmed'=>1)); - $database = m::mock('DatabaseManager'); - $database->shouldReceive('connection') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('table') - ->with( 'users' ) - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('where') - ->with('id','3') - ->andReturn( $database ) - ->once() - - ->getMock()->shouldReceive('update') - ->with(array('confirmed'=>1)) - ->andReturn( 0 ) - ->once(); - - $this->repo->app['db'] = $database; - - // Actually change the user password - $this->assertTrue( - $this->repo->confirmUser($confide_user) - ); - } - - /** - * Returns a mocked ConfideUser object for testing purposes - * only - * - * @return Illuminate\Auth\UserInterface A mocked confide user - */ - private function mockConfideUser() - { - $confide_user = m::mock( 'Illuminate\Auth\UserInterface' ); - $confide_user->username = 'uname'; - $confide_user->password = '123123'; - $confide_user->confirmed = 0; - $confide_user->shouldReceive('where','get', 'orWhere','first', 'all','getUserFromCredsIdentity') - ->andReturn( $confide_user ); - - return $confide_user; - } - - /** - * Mocks the application components that - * are not Confide's responsibility - * - * @return object Mocked laravel application - */ - private function mockApp() - { - // Mocks the application components that - // are not Confide's responsibility - $app = array(); - - $app['config'] = m::mock( 'Config' ); - $app['config']->shouldReceive( 'get' ) - ->with( 'auth.table' ) - ->andReturn( 'users' ); - - $app['config']->shouldReceive( 'get' ) - ->with( 'auth.model' ) - ->andReturn( '_mockedUser' ); - - $app['config']->shouldReceive( 'get' ) - ->with( 'app.key' ) - ->andReturn( '123' ); - - $app['config']->shouldReceive( 'get' ) - ->with( 'confide::throttle_limit' ) - ->andReturn( 9 ); - - $app['config']->shouldReceive( 'get' ) - ->andReturn( 'confide::login' ); - - $app['mail'] = m::mock( 'Mail' ); - $app['mail']->shouldReceive('send') - ->andReturn( null ); - - $app['hash'] = m::mock( 'Hash' ); - $app['hash']->shouldReceive('make') - ->andReturn( 'aRandomHash' ); - - $app['cache'] = m::mock( 'Cache' ); - $app['cache']->shouldReceive('get') - ->andReturn( 0 ); - $app['cache']->shouldReceive('put'); - - $app['auth'] = m::mock( 'Auth' ); - $app['auth']->shouldReceive('login') - ->andReturn( true ); - - $app['request'] = m::mock( 'Request' ); - $app['request']->shouldReceive('server') - ->andReturn( null ); - - $app['db'] = m::mock( 'DatabaseManager' ); - $app['db']->shouldReceive('connection') - ->andReturn( $app['db'] ); - $app['db']->shouldReceive('table') - ->andReturn( $app['db'] ); - $app['db']->shouldReceive('select') - ->andReturn( $app['db'] ); - $app['db']->shouldReceive('where') - ->andReturn( $app['db'] ); - $app['db']->shouldReceive('first') - ->andReturn( $app['db'] ); - $app['db']->email = 'test@example.com'; - - $app['db']->shouldReceive('delete') - ->andReturn( true ); - - return $app; - } - -} - -class _mockedUser {} diff --git a/tests/ConfideTest.php b/tests/ConfideTest.php deleted file mode 100644 index 228be54..0000000 --- a/tests/ConfideTest.php +++ /dev/null @@ -1,380 +0,0 @@ -mockApp(); - $repo = m::mock(new ConfideEloquentRepository); - - $this->confide = new Confide($repo); - - // Set the app attribute with mock - $this->confide->app = $app; - } - - public function testGetModel() - { - // Mocks an User object - $modelObject = $this->mockConfideUser(); - - // Mocks the repository - // Repository should receive model and return an existent user - $this->confide->repo->shouldReceive('model') - ->andReturn($modelObject) - ->once(); - - $user = $this->confide->model(); - - $this->assertEquals( $modelObject, $user ); - } - - public function testShouldGetUser() - { - $confide_user = $this->mockConfideUser(); - - // Laravel auth component should return user - $auth = m::mock('Illuminate\Auth\Guard'); - $auth->shouldReceive('user') - ->andReturn( $confide_user ) - ->once(); - $this->confide->app['auth'] = $auth; - - $this->assertEquals( $confide_user, $this->confide->user() ); - } - - public function testShouldConfirm() - { - // Mocks the repository - // Confirm method of repository should be called with confirm code - // The actual confirmation is tested within ConfideRepositoryTest ;) - $this->confide->repo->shouldReceive('confirm') - ->with('123123') - ->andReturn(true) - ->once(); - - $this->assertTrue( $this->confide->confirm( '123123' ) ); - } - - public function testShouldLogAttempt() - { - $confide_user = $this->mockConfideUser(); - - $this->confide->repo->model = $confide_user; - - $credentials = array( - 'email'=>'mail', - 'username'=>'uname', - 'password'=>'123123' - ); - - // Considering a valid hash check from hash component - $hash = m::mock('Illuminate\Hashing\HasherInterface'); - $hash->shouldReceive('check') - ->andReturn( true ); - $this->confide->app['hash'] = $hash; - - // Mocks the repository - // Repository should receive model and return an existent user - $this->confide->repo->shouldReceive('getUserByIdentity') - ->andReturn($confide_user); - - $this->confide->repo->shouldReceive('model') - ->andReturn($confide_user); - - $this->assertTrue( - $this->confide->logAttempt( $credentials ) - ); - - // Should not login with unconfirmed user. - $this->assertFalse( - $this->confide->logAttempt( $credentials, true ) - ); - - $confide_user->confirmed = 1; - - // Should login because now the user is confirmed - $this->assertTrue( - $this->confide->logAttempt( $credentials, true ) - ); - } - - public function testShouldThrottleLogAttempt() - { - $tries = 15; - - $this->confide->app['request'] = m::mock( 'Request' ); - $this->confide->app['request']->shouldReceive('server') - ->andReturn( '12.34.56.78' ); - - $confide_user = $this->mockConfideUser(); - $confide_user->shouldReceive('get','first') - ->andReturn( null ); - - // Mocks the repository - // Repository should receive getUserByIdentity and return an existent user - $this->confide->repo->shouldReceive('getUserByIdentity') - ->andReturn($confide_user); - - $this->confide->app['hash']->shouldReceive('check') - ->andReturn( false ); - - for ($i=0; $i < $tries; $i++) { - - // Simulates cache values - $this->useCacheForThrottling($i); - - // Make shure the login is not happening anyway - $this->assertFalse( - $this->confide->logAttempt( array('email'=>'wrong', 'username'=>'wrong', 'password'=>'wrong') ) - ); - } - - // Check if the credentials has been throttled - $this->assertTrue( - $this->confide->isThrottled( array('email'=>'wrong', 'password'=>'wrong') ) - ); - } - - public function testShouldForgotPassword() - { - // Mocks an User object - $confide_user = $this->mockConfideUser(); - $confide_user->shouldReceive('forgotPassword') - ->once(); - - // Mocks the repository - // Repository should receive model and return an existent user - $this->confide->repo->shouldReceive('getUserByMail') - ->with( 'user@sample.com' ) - ->andReturn($confide_user) - ->once(); - - $result = $this->confide->forgotPassword( 'user@sample.com' ); - - $this->assertTrue( $result ); - } - - public function testIsValidToken() - { - // Mocks the repository - // Repository should receive model and return an existent user - $this->confide->repo->shouldReceive('getPasswordRemindersCount') - ->with( '123' ) - ->andReturn(true) - ->once(); - - $this->assertTrue( $this->confide->isValidToken('123') ); - } - - public function testShouldResetPassword() - { - $params = array( 'token'=>'123', 'user'=>'somebody' ); - - // Mocks an User object - $confide_user = $this->mockConfideUser(); - $confide_user->shouldReceive('resetPassword') - ->with( $params ) - ->andReturn(true) - ->once(); - - // Mocks the repository - // Repository should run methods related to password reset - $this->confide->repo - ->shouldReceive('getEmailByReminderToken') - ->with( '123' ) - ->andReturn('somebody@sample.com') - ->once() - - ->getMock() - ->shouldReceive('getUserByMail') - ->with( 'somebody@sample.com' ) - ->andReturn($confide_user) - ->once() - - ->getMock() - ->shouldReceive('deleteEmailByReminderToken') - ->with( '123' ) - ->andReturn(true) - ->once(); - - $this->assertTrue( - $this->confide->resetPassword( $params ) - ); - } - - public function testShouldLogout() - { - // Make shure auth logout method is called - $auth = m::mock('Illuminate\Auth\Guard'); - $auth->shouldReceive('logout') - ->once(); - - $this->confide->app['auth'] = $auth; - $this->assertNull( $this->confide->logout() ); - } - - public function testShouldMakeForms() - { - // Make shure view make method is called 3 times - $view = m::mock('Illuminate\View\Environment\View'); - $view->shouldReceive('make') - ->andReturn( 'Content' ) - ->times( 3 ); - - $this->confide->app['view'] = $view; - - $this->assertNotNull( $this->confide->MakeLoginForm() ); - $this->assertNotNull( $this->confide->makeSignupForm() ); - $this->assertNotNull( $this->confide->makeForgotPasswordForm() ); - } - - public function testShouldCheckAction() - { - // Make shure auth logout method is called - $url = m::mock('Url'); - $url->shouldReceive('action') - ->with('a@b','b','c') - ->andReturn('a/b') - ->once(); - - $this->confide->app['url'] = $url; - $this->assertNotNull( $this->confide->checkAction('a@b','b','c') ); - } - - private function mockConfideUser() - { - $confide_user = m::mock( 'Illuminate\Auth\UserInterface' ); - $confide_user->username = 'uname'; - $confide_user->password = '123123'; - $confide_user->confirmed = 0; - $confide_user->shouldReceive('where','get', 'orWhere','first', 'all','getUserFromCredsIdentity') - ->andReturn( $confide_user ); - - return $confide_user; - } - - private function mockApp() - { - // Mocks the application components that - // are not Confide's responsibility - $app = array(); - - $app['config'] = m::mock( 'Config' ); - $app['config']->shouldReceive( 'get' ) - ->with( 'auth.table' ) - ->andReturn( 'users' ); - - $app['config']->shouldReceive( 'get' ) - ->with( 'auth.model' ) - ->andReturn( 'User' ); - - $app['config']->shouldReceive( 'get' ) - ->with( 'app.key' ) - ->andReturn( '123' ); - - $app['config']->shouldReceive( 'get' ) - ->with( 'confide::throttle_limit' ) - ->andReturn( 9 ); - - $app['config']->shouldReceive( 'get' ) - ->with( 'confide::login_cache_field' ) - ->andReturn( 'email' ); - - $app['config']->shouldReceive( 'get' ) - ->with( 'confide::throttle_time_period' ) - ->andReturn( 2 ); - - $app['config']->shouldReceive( 'get' ) - ->with( 'confide::password_field', 'password' ) - ->andReturn( 'password' ); - - $app['config']->shouldReceive( 'get' ) - ->andReturn( 'confide::login' ); - - $app['mail'] = m::mock( 'Mail' ); - $app['mail']->shouldReceive('send') - ->andReturn( null ); - - $app['hash'] = m::mock( 'Hash' ); - $app['hash']->shouldReceive('make') - ->andReturn( 'aRandomHash' ); - - $app['cache'] = m::mock( 'Cache' ); - $app['cache']->shouldReceive('get') - ->andReturn( 0 ); - $app['cache']->shouldReceive('put'); - - $app['auth'] = m::mock( 'Auth' ); - $app['auth']->shouldReceive('login') - ->andReturn( true ); - - $app['request'] = m::mock( 'Request' ); - $app['request']->shouldReceive('server') - ->andReturn( null ); - - $app['db'] = m::mock( 'DatabaseManager' ); - $app['db']->shouldReceive('connection') - ->andReturn( $app['db'] ); - $app['db']->shouldReceive('table') - ->andReturn( $app['db'] ); - $app['db']->shouldReceive('select') - ->andReturn( $app['db'] ); - $app['db']->shouldReceive('where') - ->andReturn( $app['db'] ); - $app['db']->shouldReceive('first') - ->andReturn( $app['db'] ); - $app['db']->email = 'test@example.com'; - - $app['db']->shouldReceive('delete') - ->andReturn( true ); - - return $app; - } - - private function useCacheForThrottling( $value ) - { - $cache = m::mock('Illuminate\Cache\Store'); - $cache->shouldReceive('put') - ->with( "confide_flogin_attempt_12.34.56.78wrong", $value+1, 2 ) - ->once(); - $cache->shouldReceive('get') - ->andReturn( $value ); - $this->confide->app['cache'] = $cache; - } - - /** - * the ObjectProvider getObject method should - * be called with $class to return $object. - * - * @param string $class - * @param mixed $obj - * @return void - */ - private function objProviderShouldReturn( $class, $obj ) - { - $obj_provider = m::mock('ObjectProvider'); - $obj_provider->shouldReceive('getObject') - ->with($class) - ->andReturn( $obj ); - - $this->confide->objectRepository = $obj_provider; - } -} diff --git a/tests/ConfideUserTest.php b/tests/ConfideUserTest.php deleted file mode 100644 index b3f904b..0000000 --- a/tests/ConfideUserTest.php +++ /dev/null @@ -1,320 +0,0 @@ -mockApp(); - - $this->confide_user = new ConfideUser; - } - - private function populateUser() - { - $this->confide_user->username = 'uname'; - $this->confide_user->password = '123123'; - $this->confide_user->email = 'mail@sample.com'; - $this->confide_user->confirmation_code = '456456'; - $this->confide_user->confirmed = true; - } - - public function testShouldGetAuthPassword() - { - $this->populateUser(); - - $this->assertEquals( $this->confide_user->password, $this->confide_user->getAuthPassword() ); - } - - public function testShouldConfirm() - { - $this->populateUser(); - - // Should call confirmUser of the repository - ConfideUser::$app['confide.repository'] = m::mock( 'ConfideRepository' ); - ConfideUser::$app['confide.repository']->shouldReceive('confirmUser') - ->with( $this->confide_user ) - ->andReturn( true ) - ->once(); - - $this->assertTrue( $this->confide_user->confirm() ); - - $this->assertEquals( 1, $this->confide_user->confirmed ); - } - - public function testShouldSendForgotPassword() - { - // Should call forgotPassword of the repository - ConfideUser::$app['confide.repository'] = m::mock( 'ConfideRepository' ); - ConfideUser::$app['confide.repository']->shouldReceive('forgotPassword') - ->with( $this->confide_user ) - ->andReturn( true ) - ->once(); - - // Should send an email once - ConfideUser::$app['mailer'] = m::mock( 'Mail' ); - ConfideUser::$app['mailer']->shouldReceive('send') - ->andReturn( null ) - ->atLeast(1); - - $this->populateUser(); - - $old_password = $this->confide_user->password; - - $this->assertTrue( $this->confide_user->forgotPassword() ); - } - - public function testShouldChangePassword() - { - $credentials = array( - 'email'=>'mail@sample.com', - 'password'=>'987987', - 'password_confirmation'=>'987987' - ); - - // Should call changePassword of the repository - ConfideUser::$app['confide.repository'] = m::mock( 'ConfideRepository' ); - ConfideUser::$app['confide.repository']->shouldReceive('changePassword') - ->with( $this->confide_user, 'aRandomHash' ) - ->andReturn( true ) - ->once(); - - // Should call validate method - ConfideUser::$app['confide.repository']->shouldReceive('validate') - ->andReturn( true ) - ->once(); - - ConfideUser::$app['confide.repository']->shouldReceive('model') - ->andReturn( new ConfideUser ) - ->once(); - - $this->populateUser(); - - $old_password = $this->confide_user->password; - - $this->assertTrue( $this->confide_user->resetPassword( $credentials ) ); - } - - public function testShouldNotChangePassword() - { - // Password should not be changed because it is empty - $credentials = array( - 'email'=>'mail@sample.com', - 'password'=>'', - 'password_confirmation'=>'' - ); - - // Should not call changePassword of the repository - ConfideUser::$app['confide.repository'] = m::mock( 'ConfideRepository' ); - ConfideUser::$app['confide.repository']->shouldReceive( 'changePassword' ) - ->never(); - - // Should call validate method - ConfideUser::$app['confide.repository']->shouldReceive('validate') - ->andReturn( false ) - ->times(4); - - ConfideUser::$app['confide.repository']->shouldReceive('model') - ->andReturn( new ConfideUser ) - ->times(4); - - $this->populateUser(); - - $this->assertFalse( $this->confide_user->resetPassword( $credentials ) ); - - // Additional asserts - // Password should not be changed because it is too short - $credentials = array( - 'email'=>'mail@sample.com', - 'password'=>'39a', - 'password_confirmation'=>'39a' - ); - $this->assertFalse( $this->confide_user->resetPassword( $credentials ) ); - - // Password should not be changed because it is too long - $credentials = array( - 'email'=>'mail@sample.com', - 'password'=>'1a2f34g5uj887n', - 'password_confirmation'=>'1a2f34g5uj887n' - ); - $this->assertFalse( $this->confide_user->resetPassword( $credentials ) ); - - // Password should not be changed because it is not confirmed - $credentials = array( - 'email'=>'mail@sample.com', - 'password'=>'987987', - 'password_confirmation'=>'562906' - ); - $this->assertFalse( $this->confide_user->resetPassword( $credentials ) ); - } - - public function testShouldNotSaveDuplicated() - { - // Make sure that userExists return 1 to simulates a duplicated user - ConfideUser::$app['confide.repository'] = m::mock( 'ConfideRepository' ); - ConfideUser::$app['confide.repository']->shouldReceive('userExists') - ->with( $this->confide_user ) - ->andReturn( 1 ) - ->once(); - - $this->populateUser(); - $this->confide_user->confirmation_code = ''; - $this->confide_user->confirmed = false; - - $this->assertFalse( $this->confide_user->save() ); - } - - public function testShouldSaveValidUser() - { - // Make sure that userExists return 0 to simulates a valid user - ConfideUser::$app['confide.repository'] = m::mock( 'ConfideRepository' ); - ConfideUser::$app['confide.repository']->shouldReceive('userExists') - ->with( $this->confide_user ) - ->andReturn( 0 ) - ->once(); - - $this->populateUser(); - $this->confide_user->confirmation_code = ''; - $this->confide_user->confirmed = false; - - $this->assertTrue( $this->confide_user->save() ); - } - - public function testShouldGenerateConfirmationCodeOnSave() - { - // Make sure that userExists return 0 - ConfideUser::$app['confide.repository'] = m::mock( 'ConfideRepository' ); - ConfideUser::$app['confide.repository']->shouldReceive('userExists') - ->with( $this->confide_user ) - ->andReturn( 0 ) - ->once(); - - // Should send an email once - ConfideUser::$app['mailer'] = m::mock( 'Mail' ); - ConfideUser::$app['mailer']->shouldReceive('send') - ->andReturn( null ) - ->once(); - - $this->populateUser(); - $this->confide_user->confirmation_code = ''; - $this->confide_user->confirmed = false; - - $old_cc = $this->confide_user->confirmation_code; - - $this->assertTrue( $this->confide_user->save() ); - - $new_cc = $this->confide_user->confirmation_code; - - // Should have generated a new confirmation code - $this->assertNotEquals( $old_cc, $new_cc ); - } - - public function testShouldNotGenerateConfirmationCodeIfExists() - { - $this->populateUser(); - - // Considering the model was already saved (this will make sure to - // not trigger the ConfideRepository::userExists method) - $this->confide_user->id = 1; - - $old_cc = $this->confide_user->confirmation_code; - - $this->assertTrue( $this->confide_user->save() ); - - $new_cc = $this->confide_user->confirmation_code; - - // Should not change confirmation code - $this->assertEquals( $old_cc, $new_cc ); - } - - private function mockApp() - { - // Mocks the application components - $app = array(); - - $app['config'] = m::mock( 'Config' ); - $app['config']->shouldReceive( 'get' ) - ->with( 'auth.table' ) - ->andReturn( 'users' ); - - $app['config']->shouldReceive( 'getEnvironment' ) - ->andReturn( 'production' ); - - $app['config']->shouldReceive( 'get' ) - ->with( 'app.key' ) - ->andReturn( '123' ); - - $app['config']->shouldReceive( 'get' ) - ->with( 'confide::throttle_limit' ) - ->andReturn( 9 ); - - $app['config']->shouldReceive( 'get' ) - ->andReturn( 'confide::login' ); - - $app['mailer'] = m::mock( 'Mail' ); - $app['mailer']->shouldReceive('send') - ->andReturn( null ); - - $app['hash'] = m::mock( 'Hash' ); - $app['hash']->shouldReceive('make') - ->andReturn( 'aRandomHash' ); - - $app['cache'] = m::mock( 'Cache' ); - $app['cache']->shouldReceive('get') - ->andReturn( false ); - $app['cache']->shouldReceive('put') - ->andReturn( true ); - - $app['translator'] = m::mock( 'Translator' ); - $app['translator']->shouldReceive('get') - ->andReturn( 'aTranslatedString' ); - - $app['db'] = m::mock( 'DatabaseManager' ); - $app['db']->shouldReceive('connection') - ->andReturn( $app['db'] ); - - $app['db']->shouldReceive('table') - ->andReturn( $app['db'] ); - - $app['db']->shouldReceive('insert') - ->andReturn( $app['db'] ); - - $app['db']->shouldReceive('where') - ->andReturn( $app['db'] ); - - $app['db']->shouldReceive('first') - ->andReturn( $app['db'] ); - - $app['db']->shouldReceive('update') - ->andReturn( true ); - - return $app; - } - -} diff --git a/tests/Zizaco/Confide/CacheLoginThrottleServiceTest.php b/tests/Zizaco/Confide/CacheLoginThrottleServiceTest.php new file mode 100644 index 0000000..213c5b8 --- /dev/null +++ b/tests/Zizaco/Confide/CacheLoginThrottleServiceTest.php @@ -0,0 +1,199 @@ +'someone@somewhere.com','password'=>'123']; + + $throttleService = m::mock('Zizaco\Confide\CacheLoginThrottleService[countThrottle, parseIdentity]',[]); + $throttleService->shouldAllowMockingProtectedMethods(); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $throttleService->shouldReceive('parseIdentity') + ->once()->with($identity) + ->andReturn(serialize(['email'=>'someone@somewhere.com'])); + + $throttleService->shouldReceive('countThrottle') + ->once()->with(serialize(['email'=>'someone@somewhere.com'])) + ->andReturn(5); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals(5, $throttleService->throttleIdentity($identity)); + } + + public function testShouldCheckIsThrottled() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $identity = ['email'=>'someone@somewhere.com','password'=>'123']; + $config = m::mock('Config'); + $app = ['config'=>$config]; + + $throttleService = m::mock('Zizaco\Confide\CacheLoginThrottleService[countThrottle,parseIdentity]',[$app]); + $throttleService->shouldAllowMockingProtectedMethods(); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $throttleService->shouldReceive('parseIdentity') + ->once()->with($identity) + ->andReturn(serialize(['email'=>'someone@somewhere.com'])); + + $throttleService->shouldReceive('countThrottle') + ->once()->with(serialize(['email'=>'someone@somewhere.com']), 0) + ->andReturn(10); // More than the limit specified bellow + + $config->shouldReceive('get') + ->once()->with('confide::throttle_limit') + ->andReturn(9); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($throttleService->isThrottled($identity)); + } + + public function testShouldCheckIsThrottledOnNonThrottled() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $identity = ['email'=>'someone@somewhere.com','password'=>'123']; + $config = m::mock('Config'); + $app = ['config'=>$config]; + + $throttleService = m::mock('Zizaco\Confide\CacheLoginThrottleService[countThrottle,parseIdentity]',[$app]); + $throttleService->shouldAllowMockingProtectedMethods(); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $throttleService->shouldReceive('parseIdentity') + ->once()->with($identity) + ->andReturn(serialize(['email'=>'someone@somewhere.com'])); + + $throttleService->shouldReceive('countThrottle') + ->once()->with(serialize(['email'=>'someone@somewhere.com']), 0) + ->andReturn(5); // Less than the limit specified bellow + + $config->shouldReceive('get') + ->once()->with('confide::throttle_limit') + ->andReturn(9); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertFalse($throttleService->isThrottled($identity)); + } + + public function testShouldParseIdentity() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $throttleService = m::mock('Zizaco\Confide\CacheLoginThrottleService[parseIdentity]',[]); + $throttleService->shouldAllowMockingProtectedMethods(); + $identity = ['email'=>'someone@somewhere.com','password'=>'123']; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $throttleService->shouldReceive('parseIdentity') + ->passthru(); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals( + serialize(['email'=>'someone@somewhere.com']), + $throttleService->parseIdentity($identity) + ); + } + + public function testShouldCountThrottle() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $idString = serialize(['email'=>'someone@somewhere.com']); + $cache = m::mock('Cache'); + $config = m::mock('Config'); + $app = ['cache'=>$cache, 'config'=>$config]; + + $throttleService = m::mock('Zizaco\Confide\CacheLoginThrottleService[countThrottle]',[$app]); + + $ttl = 3; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $cache->shouldReceive('get') + ->once()->with('login_throttling:'.md5($idString), 0) + ->andReturn(1); + + $config->shouldReceive('get') + ->once()->with('confide::throttle_time_period') + ->andReturn($ttl); + + $cache->shouldReceive('put') + ->once()->with('login_throttling:'.md5($idString), 2, $ttl) + ->andReturn(1); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals(2, $throttleService->countThrottle($idString)); + } +} diff --git a/tests/Zizaco/Confide/ConfideTest.php b/tests/Zizaco/Confide/ConfideTest.php new file mode 100644 index 0000000..0b0aab7 --- /dev/null +++ b/tests/Zizaco/Confide/ConfideTest.php @@ -0,0 +1,940 @@ +shouldReceive('model') + ->once()->andReturn($modelInstance); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals($modelInstance, $confide->model()); + } + + public function testShouldGetUser() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $app['auth'] = m::mock('Auth'); + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + $confide = new Confide($repo, $passService, $loginThrottler, $app); + $user = m::mock('_mockedUser'); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $app['auth']->shouldReceive('user') + ->once()->andReturn($user); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals($user, $confide->user()); + } + + public function testShouldConfirm() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + $confide = new Confide($repo, $passService, $loginThrottler, $app); + $modelInstance = m::mock('_mockedUser'); + $code = '12345'; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $repo->shouldReceive('confirmByCode') + ->once()->with($code) + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($confide->confirm($code)); + } + + public function testShouldGetUserByEmailOrUsername() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + $confide = m::mock( + 'Zizaco\Confide\Confide'. + '[extractIdentityFromArray]', + [$repo, $passService, $loginThrottler, $app] + ); + $confide->shouldAllowMockingProtectedMethods(); + + $identity = ['email'=>'johndoe@example.com']; + $user = m::mock('_mockedUser'); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $confide->shouldReceive('extractIdentityFromArray') + ->once()->with($identity) + ->andReturn($identity['email']); + + $repo->shouldReceive('getUserByEmailOrUsername') + ->once()->with('johndoe@example.com') + ->andReturn($user); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals( + $user, + $confide->getUserByEmailOrUsername($identity) + ); + } + + public function testShouldDoLogAttempt() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $app['auth'] = m::mock('Auth'); + $app['hash'] = m::mock('Hash'); + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + + $confide = m::mock( + 'Zizaco\Confide\Confide'. + '[extractRememberFromArray,extractIdentityFromArray,loginThrottling]', + [$repo, $passService, $loginThrottler, $app] + ); + $confide->shouldAllowMockingProtectedMethods(); + + $user = m::mock('_mockedUser'); + $user->confirmed = true; + $user->email = 'someone@somewhere.com'; + $user->password = 'secret'; + + $remember = true; + $input = [ + 'email'=>$user->email, + 'password'=>$user->password, + 'remember'=>$remember + ]; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $confide->shouldReceive('extractRememberFromArray') + ->with($input)->andReturn(true); + + $confide->shouldReceive('extractIdentityFromArray') + ->with($input)->andReturn($user->email); + + $confide->shouldReceive('loginThrottling') + ->once()->with($user->email) + ->andReturn(true); + + $repo->shouldReceive('getUserByEmailOrUsername') + ->once()->with('someone@somewhere.com') + ->andReturn($user); + + $app['hash']->shouldReceive('check') + ->once()->with($user->password, $user->password) + ->andReturn(true); + + $app['auth']->shouldReceive('login') + ->once()->with($user, $remember) + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($confide->logAttempt($input)); + } + + public function testShouldFailLogAttemptIfThrottled() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $app['auth'] = m::mock('Auth'); + $app['hash'] = m::mock('Hash'); + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + + $confide = m::mock( + 'Zizaco\Confide\Confide'. + '[extractRememberFromArray,extractIdentityFromArray,loginThrottling]', + [$repo, $passService, $loginThrottler, $app] + ); + $confide->shouldAllowMockingProtectedMethods(); + + $user = m::mock('_mockedUser'); + $user->confirmed = true; + $user->email = 'someone@somewhere.com'; + $user->password = 'secret'; + + $remember = true; + $input = [ + 'email'=>$user->email, + 'password'=>$user->password, + 'remember'=>$remember + ]; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $confide->shouldReceive('extractRememberFromArray') + ->with($input)->andReturn(true); + + $confide->shouldReceive('extractIdentityFromArray') + ->with($input)->andReturn($user->email); + + $confide->shouldReceive('loginThrottling') + ->once()->with($user->email) + ->andReturn(false); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertFalse($confide->logAttempt($input)); + } + + public function testShouldFailLogAttemptIfUserNotFound() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $app['auth'] = m::mock('Auth'); + $app['hash'] = m::mock('Hash'); + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + + $confide = m::mock( + 'Zizaco\Confide\Confide'. + '[extractRememberFromArray,extractIdentityFromArray,loginThrottling]', + [$repo, $passService, $loginThrottler, $app] + ); + $confide->shouldAllowMockingProtectedMethods(); + + $user = m::mock('_mockedUser'); + $user->confirmed = true; + $user->email = 'someone@somewhere.com'; + $user->password = 'secret'; + + $remember = true; + $input = [ + 'email'=>$user->email, + 'password'=>$user->password, + 'remember'=>$remember + ]; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $confide->shouldReceive('extractRememberFromArray') + ->with($input)->andReturn(true); + + $confide->shouldReceive('extractIdentityFromArray') + ->with($input)->andReturn($user->email); + + $confide->shouldReceive('loginThrottling') + ->once()->with($user->email) + ->andReturn(true); + + $repo->shouldReceive('getUserByEmailOrUsername') + ->once()->with('someone@somewhere.com') + ->andReturn(false); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertFalse($confide->logAttempt($input)); + } + + public function testShouldFailLogAttemptIfUserNotConfirmed() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $app['auth'] = m::mock('Auth'); + $app['hash'] = m::mock('Hash'); + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + + $confide = m::mock( + 'Zizaco\Confide\Confide'. + '[extractRememberFromArray,extractIdentityFromArray,loginThrottling]', + [$repo, $passService, $loginThrottler, $app] + ); + $confide->shouldAllowMockingProtectedMethods(); + + $user = m::mock('_mockedUser'); + $user->confirmed = false; + $user->email = 'someone@somewhere.com'; + $user->password = 'secret'; + + $remember = true; + $input = [ + 'email'=>$user->email, + 'password'=>$user->password, + 'remember'=>$remember + ]; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $confide->shouldReceive('extractRememberFromArray') + ->with($input)->andReturn(true); + + $confide->shouldReceive('extractIdentityFromArray') + ->with($input)->andReturn($user->email); + + $confide->shouldReceive('loginThrottling') + ->once()->with($user->email) + ->andReturn(true); + + $repo->shouldReceive('getUserByEmailOrUsername') + ->once()->with('someone@somewhere.com') + ->andReturn($user); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertFalse($confide->logAttempt($input)); + } + + public function testShouldFailLogAttemptIfWrongPass() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $app['auth'] = m::mock('Auth'); + $app['hash'] = m::mock('Hash'); + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + + $confide = m::mock( + 'Zizaco\Confide\Confide'. + '[extractRememberFromArray,extractIdentityFromArray,loginThrottling]', + [$repo, $passService, $loginThrottler, $app] + ); + $confide->shouldAllowMockingProtectedMethods(); + + $user = m::mock('_mockedUser'); + $user->confirmed = true; + $user->email = 'someone@somewhere.com'; + $user->password = 'secret'; + + $remember = true; + $input = [ + 'email'=>$user->email, + 'password'=>$user->password, + 'remember'=>$remember + ]; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $confide->shouldReceive('extractRememberFromArray') + ->with($input)->andReturn(true); + + $confide->shouldReceive('extractIdentityFromArray') + ->with($input)->andReturn($user->email); + + $confide->shouldReceive('loginThrottling') + ->once()->with($user->email) + ->andReturn(true); + + $repo->shouldReceive('getUserByEmailOrUsername') + ->once()->with('someone@somewhere.com') + ->andReturn($user); + + $app['hash']->shouldReceive('check') + ->once()->with($user->password, $user->password) + ->andReturn(false); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertFalse($confide->logAttempt($input)); + } + + public function testShouldDoLogAttemptIfNotConfirmed() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $app['auth'] = m::mock('Auth'); + $app['hash'] = m::mock('Hash'); + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + + $confide = m::mock( + 'Zizaco\Confide\Confide'. + '[extractRememberFromArray,extractIdentityFromArray,loginThrottling]', + [$repo, $passService, $loginThrottler, $app] + ); + $confide->shouldAllowMockingProtectedMethods(); + + $user = m::mock('_mockedUser'); + $user->confirmed = false; + $user->email = 'someone@somewhere.com'; + $user->password = 'secret'; + + $remember = true; + $input = [ + 'email'=>$user->email, + 'password'=>$user->password, + 'remember'=>$remember + ]; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $confide->shouldReceive('extractRememberFromArray') + ->with($input)->andReturn(true); + + $confide->shouldReceive('extractIdentityFromArray') + ->with($input)->andReturn($user->email); + + $confide->shouldReceive('loginThrottling') + ->once()->with($user->email) + ->andReturn(true); + + $repo->shouldReceive('getUserByEmailOrUsername') + ->once()->with('someone@somewhere.com') + ->andReturn($user); + + $app['hash']->shouldReceive('check') + ->once()->with($user->password, $user->password) + ->andReturn(true); + + $app['auth']->shouldReceive('login') + ->once()->with($user, $remember) + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($confide->logAttempt($input, false)); + } + + public function testShouldExtractRememberFromArray() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + + $confide = m::mock( + 'Zizaco\Confide\Confide'. + '[extractRememberFromArray]', + [$repo, $passService, $loginThrottler, $app] + ); + $confide->shouldAllowMockingProtectedMethods(); + + $inputWithoutRemember = []; + $inputWithRemember = [ + 'remember' => true + ]; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $confide->shouldReceive('extractRememberFromArray') + ->passthru(); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue( + $confide->extractRememberFromArray($inputWithRemember) + ); + $this->assertFalse( + $confide->extractRememberFromArray($inputWithoutRemember) + ); + } + + public function testShouldExtractIdentityFromArray() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + + $confide = m::mock( + 'Zizaco\Confide\Confide'. + '[extractIdentityFromArray]', + [$repo, $passService, $loginThrottler, $app] + ); + $confide->shouldAllowMockingProtectedMethods(); + + $emptyId = ['garbage'=>'dontNeed']; + $userId = ['username' => 'someone', 'garbage'=>'dontNeed']; + $emailId = ['email' => 'someone@somewhere.com', 'garbage'=>'dontNeed']; + $bothId = ['email' => 'someone@somewhere.com', 'username' => 'someone', 'garbage'=>'dontNeed']; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $confide->shouldReceive('extractIdentityFromArray') + ->passthru(); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals( + false, + $confide->extractIdentityFromArray($emptyId) + ); + $this->assertEquals( + 'someone', + $confide->extractIdentityFromArray($userId) + ); + $this->assertEquals( + 'someone@somewhere.com', + $confide->extractIdentityFromArray($emailId) + ); + $this->assertEquals( + 'someone@somewhere.com', + $confide->extractIdentityFromArray($bothId) + ); + } + + public function testShouldDoLoginThrottling() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $config = m::mock('Config'); + $app = ['config'=>$config]; + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + + $confide = m::mock( + 'Zizaco\Confide\Confide'. + '[loginThrottling]', + [$repo, $passService, $loginThrottler, $app] + ); + $confide->shouldAllowMockingProtectedMethods(); + + $userEmail = 'someone@somewhere.com'; + $throttledUserEmail = 'hack@me.com'; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $confide->shouldReceive('loginThrottling') + ->passthru(); + + $config->shouldReceive('get') + ->twice()->with('confide::throttle_limit') + ->andReturn(19); + + $loginThrottler->shouldReceive('throttleIdentity') + ->once()->with($userEmail) + ->andReturn(0); + + $loginThrottler->shouldReceive('throttleIdentity') + ->once()->with($throttledUserEmail) + ->andReturn(20); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($confide->loginThrottling($userEmail)); + $this->assertFalse($confide->loginThrottling($throttledUserEmail)); + } + + public function testIsThrottled() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + + $confide = m::mock( + 'Zizaco\Confide\Confide[isThrottled]', + [$repo, $passService, $loginThrottler, $app] + ); + + $userEmail = 'someone@somewhere.com'; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $confide->shouldReceive('isThrottled') + ->passthru(); + + $loginThrottler->shouldReceive('isThrottled') + ->once()->with($userEmail) + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($confide->isThrottled($userEmail)); + } + + public function testShouldForgotPassword() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + $confide = new Confide($repo, $passService, $loginThrottler, $app); + + $user = m::mock('Illuminate\Auth\Reminders\RemindableInterface'); + $user->email = 'someone@somewhere.com'; + $generatedToken = '12345'; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $repo->shouldReceive('getUserByEmail') + ->once()->with($user->email) + ->andReturn($user); + + $repo->shouldReceive('getUserByEmail') + ->andReturn(false); + + $passService->shouldReceive('requestChangePassword') + ->once()->with($user) + ->andReturn($generatedToken); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals( + $generatedToken, + $confide->forgotPassword($user->email) + ); + + $this->assertFalse($confide->forgotPassword('wrong@somewhere.com')); + } + + public function testShouldDestroyForgotPasswordToken() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + + $confide = m::mock( + 'Zizaco\Confide\Confide[destroyForgotPasswordToken]', + [$repo, $passService, $loginThrottler, $app] + ); + + $token = '123456789'; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $confide->shouldReceive('destroyForgotPasswordToken') + ->passthru(); + + $passService->shouldReceive('destroyToken') + ->once()->with($token) + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($confide->destroyForgotPasswordToken($token)); + } + + public function testShouldGetUserByPasswordResetToken() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + $confide = new Confide($repo, $passService, $loginThrottler, $app); + + $user = m::mock('_mockedUser'); + $user->email = 'someone@somewhere.com'; + $token = '12345'; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $passService->shouldReceive('getEmailByToken') + ->once()->with($token) + ->andReturn($user->email); + + $passService->shouldReceive('getEmailByToken') + ->andReturn(false); + + $repo->shouldReceive('getUserByEmail') + ->once()->with($user->email) + ->andReturn($user); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals( + $user, + $confide->userByResetPasswordToken($token) + ); + + $this->assertFalse( + $confide->userByResetPasswordToken('wrong') + ); + } + + public function testShouldDoLogout() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $app['auth'] = m::mock('Auth'); + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + $confide = new Confide($repo, $passService, $loginThrottler, $app); + $user = m::mock('_mockedUser'); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $app['auth']->shouldReceive('logout') + ->once()->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($confide->logout()); + } + + public function testShouldMakeViews() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $app['config'] = m::mock('Config'); + $app['view'] = m::mock('ViewEnv'); + $repo = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\PasswordServiceInterface'); + $loginThrottler = m::mock('Zizaco\Confide\LoginThrottleServiceInterface'); + $confide = new Confide($repo, $passService, $loginThrottler, $app); + $token = '12345'; + $view = m::mock('View'); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $app['view']->shouldReceive('make') + ->once()->with('view.confide::login_form') + ->andReturn($view); + $app['view']->shouldReceive('make') + ->once()->with('view.confide::signup_form') + ->andReturn($view); + $app['view']->shouldReceive('make') + ->once()->with('view.confide::forgot_password_form') + ->andReturn($view); + $app['view']->shouldReceive('make') + ->once()->with( + 'view.confide::reset_password_form', + ['token'=>$token] + ) + ->andReturn($view); + + $app['config']->shouldReceive('get') + ->times(4)->andReturnUsing(function($name){ + return 'view.'.$name; + }); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals($view, $confide->makeLoginForm()); + $this->assertEquals($view, $confide->makeSignupForm()); + $this->assertEquals($view, $confide->makeForgotPasswordForm()); + $this->assertEquals($view, $confide->makeResetPasswordForm($token)); + } +} diff --git a/tests/Zizaco/Confide/ConfideUserTest.php b/tests/Zizaco/Confide/ConfideUserTest.php new file mode 100644 index 0000000..785df8c --- /dev/null +++ b/tests/Zizaco/Confide/ConfideUserTest.php @@ -0,0 +1,341 @@ +confirmation_code = '12345'; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + ConfideFacade::shouldReceive('confirm') + ->once()->with($user->confirmation_code) + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $user->confirm(); + } + + public function testShouldForgotPassword() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $user = new _ConfideUserStub; + $user->email = 'someone@somewhere.com'; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + ConfideFacade::shouldReceive('forgotPassword') + ->once()->with($user->email) + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $user->forgotPassword(); + } + + public function testIsValid() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $user = new _ConfideUserStub; + $validator = m::mock('Zizaco\Confide\Validator'); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + App::shouldReceive('make') + ->once()->with('confide.user_validator') + ->andReturn($validator); + + $validator->shouldReceive('validate') + ->once()->with($user) + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($user->isValid()); + } + + public function testShouldValidateAndSave() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $user = m::mock('Zizaco\Confide\_ConfideUserStub[isValid,save,newQueryWithoutScopes]'); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $user->shouldReceive('save') + ->once() + ->passthru(); + + $user->shouldReceive('isValid') + ->once() + ->andReturn(true); + + // Throw an exception instead of actually saving the object + $user->shouldReceive('newQueryWithoutScopes') + ->once() + ->andReturnUsing(function(){ + throw new \Exception('Saved in database'); + }); + + // Set the exception as expected ;) + $this->setExpectedException( + 'Exception', 'Saved in database' + ); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $user->save(); + } + + public function testShouldNotSaveInvalid() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $user = m::mock('Zizaco\Confide\_ConfideUserStub[isValid,save,newQueryWithDeleted]'); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $user->shouldReceive('save') + ->once() + ->passthru(); + + $user->shouldReceive('isValid') + ->once() + ->andReturn(false); // If validation returns false + + // Throw an exception instead of actually saving the object + $user->shouldReceive('newQueryWithDeleted') + ->never() + ->andReturnUsing(function(){ + throw new \Exception('Saved in database'); + }); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertFalse($user->save()); + } + + public function testShouldGetErrors() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $user = new _ConfideUserStub; + $newMessageBag = m::mock('Illuminate\Support\MessageBag'); + $existentMessageBag = m::mock('Illuminate\Support\MessageBag'); + $user->errors = $existentMessageBag; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + App::shouldReceive('make') + ->once()->with('Illuminate\Support\MessageBag') + ->andReturn($newMessageBag); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals($existentMessageBag, $user->errors()); + $user->errors = null; + $this->assertEquals($newMessageBag, $user->errors()); + } + + public function testShouldGetAuthIdentifier() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $user = m::mock('Zizaco\Confide\_ConfideUserStub[getKey]'); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $user->shouldReceive('getKey') + ->once() + ->andReturn(1); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals(1, $user->getAuthIdentifier()); + } + + public function testShouldGetAuthPassword() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $user = new _ConfideUserStub; + $user->password = '1234'; + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals('1234', $user->getAuthPassword()); + } + + public function testShouldGetRememberToken() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $user = new _ConfideUserStub; + $user->remember_token = '1234'; + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals('1234', $user->getRememberToken()); + } + + public function testShouldSetRememberToken() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $user = new _ConfideUserStub; + $user->remember_token = null; + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $user->setRememberToken('123'); + $this->assertEquals('123', $user->remember_token); + } + + public function testShouldGetRememberTokenName() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $user = new _ConfideUserStub; + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals('remember_token', $user->getRememberTokenName()); + } + + public function testShouldGetReminderEmail() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $user = new _ConfideUserStub; + $user->email = 'someone@somewhere.com'; + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals('someone@somewhere.com', $user->getReminderEmail()); + } +} + +/** + * A stub class that implements ConfideUserInterface and uses + * the ConfideUser trait. + * + * @see \Zizaco\Confide\ConfideUser + */ +class _ConfideUserStub extends Eloquent implements ConfideUserInterface{ + use ConfideUser; +} diff --git a/tests/Zizaco/Confide/EloquentPasswordServiceTest.php b/tests/Zizaco/Confide/EloquentPasswordServiceTest.php new file mode 100644 index 0000000..5805df1 --- /dev/null +++ b/tests/Zizaco/Confide/EloquentPasswordServiceTest.php @@ -0,0 +1,453 @@ +shouldAllowMockingProtectedMethods(); + + $passService->app['db'] = $db; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + // Since the user implements the RemindableInterface. + // See: http://laravel.com/docs/security#password-reminders-and-reset + $user->shouldReceive('getReminderEmail') + ->andReturn($userEmail); + + // Retrieve the connection name that is being used in the user model + $user->shouldReceive('getConnectionName') + ->andReturn('db_name'); + + // The PasswordService generate token method is the responsible + // for generating tokens + $passService->shouldReceive('generateToken') + ->andReturn($generatedToken); + + // The email containing the reset link should be sent + $passService->shouldReceive('sendEmail') + ->once()->with($user, $generatedToken); + + // Mocks DB in order to check for the following query: + // DB::connection('db_name') + // ->table('password_reminders')->insert(array( + // 'email'=> $email, + // 'token'=> $token, + // 'created_at'=> new \DateTime + // )); + $db->shouldReceive('connection') + ->once()->with('db_name') + ->andReturn( $db ); + + $db->shouldReceive('table') + ->with( 'password_reminders' ) + ->once() + ->andReturn( $db ); + + $db->shouldReceive('insert') + ->with([ + 'email' => $userEmail, + 'token' => $generatedToken, + 'created_at' => new \DateTime + ]) + ->once() + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals( + $generatedToken, + $passService->requestChangePassword($user) + ); + } + + public function testShouldGetEmailByToken() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $userEmail = 'someone@somewhere.com'; + $token = '123456789'; + $oldestValidDate = '2014-07-16 22:20:26'; + $passService = m::mock('Zizaco\Confide\EloquentPasswordService'); + $db = m::mock('connection'); + $userModel = m::mock('Zizaco\Confide\ConfideUserInterface'); + + $passService->shouldAllowMockingProtectedMethods(); + $passService->app['db'] = $db; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $passService->shouldReceive('getEmailByToken') + ->passthru(); + + $passService->shouldReceive('getConnection') + ->once()->andReturn('db_name'); + + $passService->shouldReceive('getOldestValidDate') + ->once()->andReturn($oldestValidDate); + + $passService->shouldReceive('unwrapEmail') + ->once()->with(['email'=>$userEmail]) + ->andReturn($userEmail); + + // Mocks DB in order to check for the following query: + // DB::connection('db_name') + // ->table('password_reminders') + // ->select('email') + // ->where('token','=',$token) + // ->first(); + $db->shouldReceive('connection') + ->once()->with('db_name') + ->andReturn( $db ); + + $db->shouldReceive('table') + ->with('password_reminders') + ->andReturn( $db ) + ->once(); + + $db->shouldReceive('select') + ->with('email') + ->andReturn( $db ) + ->once(); + + $db->shouldReceive('where') + ->with('token', '=', $token) + ->andReturn( $db ) + ->once(); + + $db->shouldReceive('where') + ->with('created_at', '>=', $oldestValidDate) + ->andReturn( $db ) + ->once(); + + $db->shouldReceive('first') + ->once() + ->andReturn(['email' => $userEmail]); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals( + $userEmail, + $passService->getEmailByToken($token) + ); + } + + public function testShouldDestroyToken() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $token = '123456789'; + $passService = m::mock('Zizaco\Confide\EloquentPasswordService'); + $db = m::mock('connection'); + + $passService->shouldAllowMockingProtectedMethods(); + $passService->app['db'] = $db; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $passService->shouldReceive('destroyToken') + ->passthru(); + + $passService->shouldReceive('getConnection') + ->once()->andReturn('db_name'); + + // Mocks DB in order to check for the following query: + // DB::connection('db_name') + // ->table('password_reminders') + // ->where('token','=',$token) + // ->delete(); + $db->shouldReceive('connection') + ->once()->with('db_name') + ->andReturn( $db ); + + $db->shouldReceive('table') + ->with('password_reminders') + ->andReturn( $db ) + ->once(); + + $db->shouldReceive('where') + ->with('token', '=', $token) + ->andReturn( $db ) + ->once(); + + $db->shouldReceive('delete') + ->once() + ->andReturn(1); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue( + $passService->destroyToken($token) + ); + } + + public function testShouldGetConnection() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $repository = m::mock('Zizaco\Confide\RepositoryInterface'); + $passService = m::mock('Zizaco\Confide\EloquentPasswordService'); + $modelInstance = m::mock('Zizaco\Confide\ConfideUserInterface'); + + $passService->shouldAllowMockingProtectedMethods(); + $passService->app['confide.repository'] = $repository; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $passService->shouldReceive('getConnection') + ->passthru(); + + $repository->shouldReceive('model') + ->once()->andReturn($modelInstance); + + $modelInstance->shouldReceive('getConnectionName') + ->once()->andReturn('db_name'); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals( + 'db_name', + $passService->getConnection() + ); + } + + public function testShouldGenerateToken() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $passService = m::mock('Zizaco\Confide\EloquentPasswordService'); + $passService->shouldAllowMockingProtectedMethods(); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $passService->shouldReceive('generateToken') + ->passthru(); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue(is_string($passService->generateToken())); + } + + public function testShouldUnwrapEmail() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $passService = m::mock('Zizaco\Confide\EloquentPasswordService'); + $passService->shouldAllowMockingProtectedMethods(); + $email = 'someone@somewhere.com'; + $emailArray = ['email'=>$email]; + $emailObject = m::mock('UserWithEmail'); + + $emailObject->email = $email; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $passService->shouldReceive('unwrapEmail') + ->passthru(); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals($email, $passService->unwrapEmail($email)); + $this->assertEquals($email, $passService->unwrapEmail($emailArray)); + $this->assertEquals($email, $passService->unwrapEmail($emailObject)); + } + + public function testShouldSendEmail() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $passService = m::mock('Zizaco\Confide\EloquentPasswordService[sendEmail]'); + $passService->shouldAllowMockingProtectedMethods(); + + $mailer = m::mock('Mailer'); + $config = m::mock('Config'); + $translator = m::mock('Translator'); + $passService->app['mailer'] = $mailer; + $passService->app['config'] = $config; + $passService->app['translator'] = $translator; + + $token = '123'; + $user = m::mock('UserWithEmail'); + $user->email = 'someone@somewhere.com'; + $user->username = 'Someone'; + + $test = $this; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $passService->shouldReceive('sendEmail') + ->passthru(); + + $mailer->shouldReceive('send') + ->once()->with('view.name', ['user'=>$user, 'token'=>$token], m::any()) + ->andReturnUsing(function($a,$b,$closure) use ($test, $user) { + $message = m::mock('Message'); + + $message->shouldReceive('to') + ->once()->with($user->email, $user->username) + ->andReturn($message); + + $message->shouldReceive('subject') + ->once()->with('the-email-subject') + ->andReturn($message); + + $closure($message); + }); + + $translator->shouldReceive('get') + ->once()->with('confide::confide.email.password_reset.subject') + ->andReturn('the-email-subject'); + + $config->shouldReceive('get') + ->once()->with('confide::email_reset_password') + ->andReturn('view.name'); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $passService->sendEmail($user, $token); + } + + public function testShouldGetOldestValidDate() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $oldestValidDate = '2014-07-16 22:20:26'; + $carbon = m::mock('Carbon\Carbon'); + $config = m::mock('Config'); + $passService = m::mock('Zizaco\Confide\EloquentPasswordService'); + + $passService->shouldAllowMockingProtectedMethods(); + $passService->app['Carbon\Carbon'] = $carbon; + $passService->app['config'] = $config; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $passService->shouldReceive('getOldestValidDate') + ->passthru(); + + $config->shouldReceive('get') + ->once()->with('confide::confide.password_reset_expiration', 7) + ->andReturn(14); + + $carbon->shouldReceive('now') + ->once()->andReturn($carbon); + + $carbon->shouldReceive('subHours') + ->once()->with(14) + ->andReturn($carbon); + + $carbon->shouldReceive('toDateTimeString') + ->once()->andReturn($oldestValidDate); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals( + $oldestValidDate, + $passService->getOldestValidDate() + ); + } +} diff --git a/tests/Zizaco/Confide/EloquentRepositoryTest.php b/tests/Zizaco/Confide/EloquentRepositoryTest.php new file mode 100644 index 0000000..5991ad8 --- /dev/null +++ b/tests/Zizaco/Confide/EloquentRepositoryTest.php @@ -0,0 +1,269 @@ +app['config'] = m::mock('Illuminate\Config\Repository'); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + // Make sure to return the wanted value from config + $repo->app['config']->shouldReceive('get') + ->with('auth.model') + ->once() + ->andReturn($modelClassName); + + // When requesting the _mockedUser in the IoC, return + // the correct object. + $repo->app[$modelClassName] = $user; + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals($user, $repo->model()); + } + + public function testShouldThrowExceptionIfCannotGetModel() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $repo = new EloquentRepository([]); + $repo->app['config'] = m::mock('Illuminate\Config\Repository'); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + // Make sure to return the wanted value from config + $repo->app['config']->shouldReceive('get') + ->with('auth.model') + ->once() + ->andReturn(null); + + // Set the exception as expected ;) + $this->setExpectedException( + 'Exception', 'Wrong model specified in config/auth.php' + ); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $repo->model(); + } + + public function testShouldGetUserByIdentity() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $identity = [ + 'email' => 'someone@somewhere.com', + 'something' => 'somevalue' + ]; + $model = m::mock('_mockedUser'); + $user = m::mock('_mockedUser'); + $repo = m::mock('Zizaco\Confide\EloquentRepository[model]',[]); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + // Repo model method should return the model instance + $repo->shouldReceive('model') + ->andReturn($model); + + // Should query for the user using each credential + $firstWhere = true; + foreach ($identity as $attribute => $value) { + $model->shouldReceive(($firstWhere) ? 'where' : 'orWhere') + ->with($attribute, '=', $value) + ->once() + ->andReturn($model); + + $firstWhere = false; + } + + $model->shouldReceive('get') + ->once() + ->andReturn($model); + + $model->shouldReceive('first') + ->once() + ->andReturn($user); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + // Should return the user + $this->assertEquals($user, $repo->getUserByIdentity($identity)); + } + + public function testShouldGetUserByEmail() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $email = 'someone@somewhere.com'; + $user = m::mock('_mockedUser'); + $repo = m::mock('Zizaco\Confide\EloquentRepository[getUserByIdentity]',[]); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + // Repo model method should return the model instance + $repo->shouldReceive('getUserByIdentity') + ->with(['email'=>$email]) + ->andReturn($user); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + // Should return the user + $this->assertEquals($user, $repo->getUserByEmail($email)); + } + + public function testShouldGetUserByEmailOrUsername() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $username = 'Someone'; + $user = m::mock('_mockedUser'); + $repo = m::mock('Zizaco\Confide\EloquentRepository[getUserByIdentity]',[]); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + // Repo model method should return the model instance + $repo->shouldReceive('getUserByIdentity') + ->with(['email'=>$username, 'username'=>strtolower($username)]) + ->andReturn($user); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + // Should return the user + $this->assertEquals($user, $repo->getUserByEmailOrUsername($username)); + } + + public function testShouldConfirmByCode() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $confirmCode = 123123; + $wrongConfirmCode = 'IdontExist'; + $user = m::mock('_mockedUser'); + $repo = m::mock('Zizaco\Confide\EloquentRepository[getUserByIdentity,confirmUser]',[]); + $repo->shouldAllowMockingProtectedMethods(); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + // Should query for the user + $repo->shouldReceive('getUserByIdentity') + ->with(['confirmation_code' => $confirmCode]) + ->andReturn($user); + + // Return null if no user can be found + $repo->shouldReceive('getUserByIdentity') + ->with(['confirmation_code' => $wrongConfirmCode]) + ->andReturn(null); + + // Should call the confirmUser method with the user + // instance + $repo->shouldReceive('confirmUser') + ->with($user) + ->once() + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($repo->confirmByCode($confirmCode)); + $this->assertFalse($repo->confirmByCode($wrongConfirmCode)); + } + + public function testShouldConfirmUser() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $user = m::mock('_mockedUser'); + $repo = m::mock('Zizaco\Confide\EloquentRepository[confirmUser]',[]); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $user->shouldReceive('save') + ->once() + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($repo->confirmUser($user)); + } +} diff --git a/tests/Zizaco/Confide/ServiceProviderTest.php b/tests/Zizaco/Confide/ServiceProviderTest.php new file mode 100644 index 0000000..2e73eb9 --- /dev/null +++ b/tests/Zizaco/Confide/ServiceProviderTest.php @@ -0,0 +1,334 @@ +shouldReceive('package') + ->with('zizaco/confide') + ->once(); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $sp->boot(); + } + + public function testShouldRegister() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $sp = m::mock( + 'Zizaco\Confide\ServiceProvider'. + '[registerRepository,registerPasswordService,'. + 'registerConfide,registerCommands,'. + 'registerLoginThrottleService,'. + 'registerUserValidator]', + ['something'] + ); + $sp->shouldAllowMockingProtectedMethods(); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $sp->shouldReceive( + 'registerRepository','registerConfide', + 'registerCommands','registerPasswordService', + 'registerLoginThrottleService', + 'registerUserValidator' + ) + ->once(); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $sp->register(); + } + + public function testShouldRegisterRepository() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $test = $this; + $app = m::mock('LaravelApp'); + $sp = m::mock('Zizaco\Confide\ServiceProvider', [$app]); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $app->shouldReceive('bind') + ->once()->andReturnUsing( + // Make sure that the name is 'confide.repository' + // and that the closure passed returns the correct + // kind of object. + function($name, $closure) use ($test, $app) { + $test->assertEquals('confide.repository', $name); + $test->assertInstanceOf( + 'Zizaco\Confide\EloquentRepository', + $closure($app) + ); + } + ); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $sp->registerRepository(); + } + + public function testShouldRegisterPasswordService() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $test = $this; + $app = m::mock('LaravelApp'); + $sp = m::mock('Zizaco\Confide\ServiceProvider', [$app]); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $app->shouldReceive('bind') + ->once()->andReturnUsing( + // Make sure that the name is 'confide.password' + // and that the closure passed returns the correct + // kind of object. + function($name, $closure) use ($test, $app) { + $test->assertEquals('confide.password', $name); + $test->assertInstanceOf( + 'Zizaco\Confide\EloquentPasswordService', + $closure($app) + ); + } + ); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $sp->registerPasswordService(); + } + + public function testShouldRegisterLoginThrottleService() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $test = $this; + $app = m::mock('LaravelApp'); + $sp = m::mock('Zizaco\Confide\ServiceProvider', [$app]); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $app->shouldReceive('bind') + ->once()->andReturnUsing( + // Make sure that the name is 'confide.throttle' + // and that the closure passed returns the correct + // kind of object. + function($name, $closure) use ($test, $app) { + $test->assertEquals('confide.throttle', $name); + $test->assertInstanceOf( + 'Zizaco\Confide\CacheLoginThrottleService', + $closure($app) + ); + } + ); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $sp->registerLoginThrottleService(); + } + + public function testShouldRegisterUserValidator() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $test = $this; + $app = m::mock('LaravelApp'); + $sp = m::mock('Zizaco\Confide\ServiceProvider', [$app]); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $app->shouldReceive('bind') + ->once()->andReturnUsing( + // Make sure that the name is 'confide.user_validator' + // and that the closure passed returns the correct + // kind of object. + function($name, $closure) use ($test, $app) { + $test->assertEquals('confide.user_validator', $name); + $test->assertInstanceOf( + 'Zizaco\Confide\UserValidator', + $closure($app) + ); + } + ); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $sp->registerUserValidator(); + } + + public function testShouldRegisterConfide() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $test = $this; + $app = m::mock('LaravelApp'); + $sp = m::mock('Zizaco\Confide\ServiceProvider', [$app]); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $app->shouldReceive('make') + ->once()->with('confide.repository') + ->andReturn(new EloquentRepository); + + $app->shouldReceive('make') + ->once()->with('confide.password') + ->andReturn(new EloquentPasswordService); + + $app->shouldReceive('make') + ->once()->with('confide.throttle') + ->andReturn(new CacheLoginThrottleService); + + $app->shouldReceive('bind') + ->once()->andReturnUsing( + // Make sure that the name is 'confide' + // and that the closure passed returns the correct + // kind of object. + function($name, $closure) use ($test, $app) { + $test->assertEquals('confide', $name); + $test->assertInstanceOf( + 'Zizaco\Confide\Confide', + $closure($app) + ); + } + ); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $sp->registerConfide(); + } + + public function testSHouldRegisterCommands() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $test = $this; + $app = m::mock('LaravelApp'); + $sp = m::mock('Zizaco\Confide\ServiceProvider', [$app]); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $app->shouldReceive('bind') + ->times(3)->andReturnUsing( + // Make sure that the commands are being registered + // with a closure that returns the correct + // object. + function($name, $closure) use ($test, $app) { + + $shouldBe = [ + 'command.confide.controller' => 'Zizaco\Confide\ControllerCommand', + 'command.confide.routes' => 'Zizaco\Confide\RoutesCommand', + 'command.confide.migration' => 'Zizaco\Confide\MigrationCommand', + ]; + + $test->assertInstanceOf( + $shouldBe[$name], + $closure($app) + ); + } + ); + + $sp->shouldReceive('commands') + ->with( + 'command.confide.controller', + 'command.confide.routes', + 'command.confide.migration' + ); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $sp->registerCommands(); + } +} diff --git a/tests/Zizaco/Confide/Support/GenerateCommandTest.php b/tests/Zizaco/Confide/Support/GenerateCommandTest.php new file mode 100644 index 0000000..fc40d59 --- /dev/null +++ b/tests/Zizaco/Confide/Support/GenerateCommandTest.php @@ -0,0 +1,140 @@ +$config, + 'view'=>$view, + 'path'=>'/where/the/app/is', + ]; + $command = m::mock('Zizaco\Confide\Support\_GenerateCommandStub[makeDir,filePutContents]', [$app]); + $command->shouldAllowMockingProtectedMethods(); + $filename = 'path/to/file.php'; + $viewName = 'generate.my_view'; + $viewVars = [ + 'someVar' => 'someValue' + ]; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $view->shouldReceive('make') + ->once() + ->with('confide::'.$viewName, $viewVars) + ->andReturn($view); + + $view->shouldReceive('render') + ->once() + ->andReturn('The rendered content'); + + $command->shouldReceive('generateFile') + ->passthru(); + + $command->shouldReceive('makeDir') + ->with("/where/the/app/is/path/to", 493, true) + ->once() + ->andReturn(true); + + $command->shouldReceive('filePutContents') + ->with("/where/the/app/is/path/to/file.php", "The rendered content") + ->once() + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $command->generateFile($filename, $viewName, $viewVars); + } + + public function testShouldAppendInFile() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $config = m::mock('Config'); + $view = m::mock('View'); + $app = [ + 'config'=>$config, + 'view'=>$view, + 'path'=>'/where/the/app/is', + ]; + $command = m::mock('Zizaco\Confide\Support\_GenerateCommandStub[makeDir,filePutContents]', [$app]); + $command->shouldAllowMockingProtectedMethods(); + $filename = 'path/to/file.php'; + $viewName = 'generate.my_view'; + $viewVars = [ + 'someVar' => 'someValue' + ]; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $view->shouldReceive('make') + ->once() + ->with('confide::'.$viewName, $viewVars) + ->andReturn($view); + + $view->shouldReceive('render') + ->once() + ->andReturn('The rendered content'); + + $command->shouldReceive('appendInFile') + ->passthru(); + + $command->shouldReceive('makeDir') + ->with("/where/the/app/is/path/to", 493, true) + ->once() + ->andReturn(true); + + $command->shouldReceive('filePutContents') + ->with("/where/the/app/is/path/to/file.php", "The rendered content", 8) + ->once() + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $command->appendInFile($filename, $viewName, $viewVars); + } +} + +/** + * A stub class that extends GenerateCommand + * + * @see \Zizaco\Confide\Support\GenerateCommand + */ +class _GenerateCommandStub extends GenerateCommand {} diff --git a/tests/Zizaco/Confide/UserValidatorTest.php b/tests/Zizaco/Confide/UserValidatorTest.php new file mode 100644 index 0000000..06282b7 --- /dev/null +++ b/tests/Zizaco/Confide/UserValidatorTest.php @@ -0,0 +1,344 @@ +with('confide.repository') + ->andReturn($repo); + + $validator = m::mock( + 'Zizaco\Confide\UserValidator'. + '[validatePassword,validateIsUnique,validateAttributes]' + ); + $validator->shouldAllowMockingProtectedMethods(); + + $user = m::mock('Zizaco\Confide\ConfideUserInterface'); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $validator->shouldReceive('validatePassword') + ->once()->andReturn(true); + + $validator->shouldReceive('validateIsUnique') + ->once()->andReturn(true); + + $validator->shouldReceive('validateAttributes') + ->once()->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($validator->validate($user)); + } + + public function testShouldValidatePassword() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $hash = m::mock('Hash'); + + App::shouldReceive('make') + ->with('hash') + ->andReturn($hash); + + $validator = m::mock('Zizaco\Confide\UserValidator[attachErrorMsg]'); + + $userA = m::mock('Zizaco\Confide\ConfideUserInterface'); + $userA->password = 'foo123'; + $userA->password_confirmation = 'foo123'; + + $userB = m::mock('Zizaco\Confide\ConfideUserInterface'); + $userB->password = 'foo123'; + $userB->password_confirmation = 'foo456'; + + $userC = m::mock('Zizaco\Confide\ConfideUserInterface'); + $userC->password = '$2y$10$8PqTle4VGODMbjFbpIe.vOISth8qAaXlO7CAi4HNneqe37Jy1gGRO'; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $userA->shouldReceive('getOriginal') + ->once()->with('password') + ->andReturn(''); + + $userB->shouldReceive('getOriginal') + ->once()->with('password') + ->andReturn('old_pass'); + + $userC->shouldReceive('getOriginal') + ->once()->with('password') + ->andReturn('$2y$10$8PqTle4VGODMbjFbpIe.vOISth8qAaXlO7CAi4HNneqe37Jy1gGRO'); + + $hash->shouldReceive('make') + ->once()->with('foo123') + ->andReturn('hashedPassword'); + + $validator->shouldReceive('attachErrorMsg') + ->atLeast(1) + ->with( + m::any(), + 'validation.confirmed::confide.alerts.wrong_confirmation', + 'password_confirmation' + ); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($validator->validatePassword($userA)); + $this->assertFalse($validator->validatePassword($userB)); + $this->assertTrue($validator->validatePassword($userC)); + } + + public function testShouldValidateIsUnique() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $repo = m::mock('Zizaco\Confide\EloquentRepository'); + + $validator = m::mock('Zizaco\Confide\UserValidator[attachErrorMsg]'); + $validator->repo = $repo; + + $userA = m::mock('Zizaco\Confide\ConfideUserInterface'); + $userA->id = 1; + $userA->email = 'zizaco@gmail.com'; + $userA->username = 'zizaco'; + + $userB = m::mock('Zizaco\Confide\ConfideUserInterface'); + $userB->id = '2'; + $userB->email = 'foo@bar.com'; + $userB->username = 'foo'; + + $userC = m::mock('Zizaco\Confide\ConfideUserInterface'); + $userC->id = '3'; + $userC->email = 'something@somewhere.com'; + $userC->username = 'something'; + + $userD = m::mock('Zizaco\Confide\ConfideUserInterface'); + $userD->id = ''; // No id + $userD->email = 'something@somewhere.com'; // Duplicated email + $userD->username = 'something'; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $userA->shouldReceive('getKey') + ->andReturn($userA->id); + + $userB->shouldReceive('getKey') + ->andReturn($userB->id); + + $userC->shouldReceive('getKey') + ->andReturn($userC->id); + + $userD->shouldReceive('getKey') + ->andReturn($userD->id); + + $repo->shouldReceive('getUserByIdentity') + ->andReturnUsing(function($user) use ($userB, $userC) { + if (isset($user['email']) && $user['email'] == $userB->email) return $userB; + if (isset($user['email']) && $user['email'] == $userC->email) return $userC; + }); + + $validator->shouldReceive('attachErrorMsg') + ->atLeast(1) + ->with(m::any(), 'confide::confide.alerts.duplicated_credentials', 'email'); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($validator->validateIsUnique($userA)); + $this->assertTrue($validator->validateIsUnique($userB)); + $this->assertTrue($validator->validateIsUnique($userC)); + $this->assertFalse($validator->validateIsUnique($userD)); + } + + public function testShouldValidateAttributes() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $laravelValidator = m::mock('Validator'); + $errorBag = m::mock('ErrorBag'); + + App::shouldReceive('make') + ->with('validator') + ->andReturn($laravelValidator); + + $validator = new UserValidator; + + $userA = m::mock('Zizaco\Confide\ConfideUserInterface'); + $userB = m::mock('Zizaco\Confide\ConfideUserInterface'); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + + $userA->shouldReceive('toArray') + ->andReturn(['username'=>'foo']); + + // Password must be retrieved separately since it may + // be hidden from toArray method. + $userA->shouldReceive('getAuthPassword') + ->andReturn('secret'); + + $userB->shouldReceive('toArray') + ->andReturn(['username'=>'bar']); + + $userB->shouldReceive('getAuthPassword') + ->andReturn('p@ss'); + + $laravelValidator->shouldReceive('make') + ->with( + ['username'=>'foo', 'password'=>'secret'], + $validator->rules['create'] + ) + ->once()->andReturn($laravelValidator); + + $laravelValidator->shouldReceive('make') + ->with( + ['username'=>'bar', 'password'=>'p@ss'], + $validator->rules['create'] + ) + ->once()->andReturn($laravelValidator); + + $laravelValidator->shouldReceive('fails') + ->twice()->andReturn(false, true); + + $laravelValidator->shouldReceive('errors') + ->once()->andReturn($errorBag); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue($validator->validateAttributes($userA)); + $this->assertFalse($validator->validateAttributes($userB)); + $this->assertEquals($errorBag, $userB->errors); + } + + public function testShouldAttachErrorMsgOnEmpty() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $errorBag = m::mock('Illuminate\Support\MessageBag'); + + App::shouldReceive('make') + ->with('Illuminate\Support\MessageBag') + ->andReturn($errorBag); + + $validator = new UserValidator; + $user = m::mock('Zizaco\Confide\ConfideUserInterface'); + $user->errors = null; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + Lang::shouldReceive('get') + ->once()->with('foobar') + ->andReturn('translated_foobar'); + + $errorBag->shouldReceive('add') + ->with('confide', 'translated_foobar') + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $validator->attachErrorMsg($user, 'foobar'); + $this->assertInstanceOf('Illuminate\Support\MessageBag', $user->errors); + } + + public function testShouldAttachErrorMsgOnExisting() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $errorBag = m::mock('Illuminate\Support\MessageBag'); + + App::shouldReceive('make') + ->with('Illuminate\Support\MessageBag') + ->never(); + + $validator = new UserValidator; + $user = m::mock('Zizaco\Confide\ConfideUserInterface'); + $user->errors = $errorBag; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + Lang::shouldReceive('get') + ->once()->with('foobar') + ->andReturn('translated_foobar'); + + $errorBag->shouldReceive('add') + ->with('confide', 'translated_foobar') + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $validator->attachErrorMsg($user, 'foobar'); + $this->assertInstanceOf('Illuminate\Support\MessageBag', $user->errors); + } +} diff --git a/tests/commands/ControllerCommandTest.php b/tests/commands/ControllerCommandTest.php new file mode 100644 index 0000000..25e5699 --- /dev/null +++ b/tests/commands/ControllerCommandTest.php @@ -0,0 +1,208 @@ +$config]; + $command = m::mock('Zizaco\Confide\ControllerCommand', [$app]); + $options = [ + ['name', null, InputOption::VALUE_OPTIONAL, 'Name of the controller.', 'Users'], + ['--restful', '-r', InputOption::VALUE_NONE, 'Generate RESTful controller.'] + ]; + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals($options, $command->getOptions()); + } + + public function testSouldFire() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $config = m::mock('Config'); + $app = ['config'=>$config]; + $command = m::mock('Zizaco\Confide\ControllerCommand', [$app]); + $command->shouldAllowMockingProtectedMethods(); + $viewVars = [ + 'class' => "UsersController", + 'namespace' => "The\\Namespace", + 'model' => "User", + 'restful' => true + ]; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $command->shouldReceive('getControllerName') + ->once()->with('The\\Namespace\\Users') + ->andReturn('UsersController'); + + $command->shouldReceive('getNamespace') + ->once()->with('The\\Namespace\\Users') + ->andReturn('The\\Namespace'); + + $config->shouldReceive('get') + ->once()->with('auth.model') + ->andReturn('User'); + + $command->shouldReceive('option') + ->twice()->with('name') + ->andReturn('The\\Namespace\\Users'); + + $command->shouldReceive('option') + ->once()->with('restful') + ->andReturn(true); + + $command->shouldReceive('fire') + ->passthru(); + + $command->shouldReceive('line','info','comment','confirm') + ->andReturn(true); + + $command->shouldReceive('generateFile') + ->once()->with( + 'controllers/The/Namespace/UsersController.php', + 'generators.controller', + $viewVars + ) + ->andReturn(true); + + $command->shouldReceive('generateFile') + ->once()->with( + 'models/UserRepository.php', + 'generators.repository', + $viewVars + ) + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $command->fire(); + } + + public function testSouldGetControlerName() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $command = m::mock('Zizaco\Confide\ControllerCommand', [$app]); + $command->shouldAllowMockingProtectedMethods(); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $command->shouldReceive('getControllerName') + ->passthru(); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals( + 'UsersController', + $command->getControllerName('Users') + ); + + $this->assertEquals( + 'UsersController', + $command->getControllerName('UsersController') + ); + + $this->assertEquals( + 'SomethingController', + $command->getControllerName('Some\Namespace\Something') + ); + + $this->assertEquals( + 'CamelCaseController', + $command->getControllerName('Some\Thing\CamelCase') + ); + } + + public function testSouldGetNamespace() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $command = m::mock('Zizaco\Confide\ControllerCommand', [$app]); + $command->shouldAllowMockingProtectedMethods(); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $command->shouldReceive('getNamespace') + ->passthru(); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertEquals( + 'Some\Namespace', + $command->getNamespace('Some\Namespace\Something') + ); + + $this->assertEquals( + 'Some\Thing', + $command->getNamespace('Some\Thing\Resource') + ); + + $this->assertEquals( + '', + $command->getNamespace('Users') + ); + } +} diff --git a/tests/commands/MigrationCommandTest.php b/tests/commands/MigrationCommandTest.php new file mode 100644 index 0000000..c343b1a --- /dev/null +++ b/tests/commands/MigrationCommandTest.php @@ -0,0 +1,92 @@ +assertEquals($options, $command->getOptions()); + } + + public function testSouldFire() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $command = m::mock('Zizaco\Confide\MigrationCommand', [$app]); + $command->shouldAllowMockingProtectedMethods(); + $viewVars = [ + 'table' => "users", + ]; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $command->shouldReceive('option') + ->once()->with('table') + ->andReturn('users'); + + $command->shouldReceive('fire') + ->passthru(); + + $command->shouldReceive('line','info','comment','confirm') + ->andReturn(true); + + $command->shouldReceive('generateFile') + ->once()->with( + 'database/migrations/'.date('Y_m_d_His').'_confide_setup_users_table.php', + 'generators.migration', + $viewVars + ) + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $command->fire(); + } +} diff --git a/tests/commands/RoutesCommandTest.php b/tests/commands/RoutesCommandTest.php new file mode 100644 index 0000000..2cbd759 --- /dev/null +++ b/tests/commands/RoutesCommandTest.php @@ -0,0 +1,131 @@ +assertEquals($options, $command->getOptions()); + } + + public function testSouldFire() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $command = m::mock('Zizaco\Confide\RoutesCommand', [$app]); + $command->shouldAllowMockingProtectedMethods(); + $viewVars = [ + 'controllerName' => 'UsersController', + 'url' => 'users', + 'restful' => true + ]; + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $command->shouldReceive('option') + ->once()->with('controller') + ->andReturn('UsersController'); + + $command->shouldReceive('option') + ->once()->with('restful') + ->andReturn(true); + + $command->shouldReceive('getFireMessage') + ->once() + ->andReturn("Some message about appending the routes..."); + + $command->shouldReceive('fire') + ->passthru(); + + $command->shouldReceive('line','info','comment','confirm') + ->andReturn(true); + + $command->shouldReceive('appendInFile') + ->once()->with( + 'routes.php', + 'generators.routes', + $viewVars + ) + ->andReturn(true); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $command->fire(); + } + + public function testShouldGetFireMessage() + { + /* + |------------------------------------------------------------ + | Set + |------------------------------------------------------------ + */ + $app = []; + $command = m::mock('Zizaco\Confide\RoutesCommand', [$app]); + $command->shouldAllowMockingProtectedMethods(); + + /* + |------------------------------------------------------------ + | Expectation + |------------------------------------------------------------ + */ + $command->shouldReceive('getFireMessage') + ->passthru(); + + /* + |------------------------------------------------------------ + | Assertion + |------------------------------------------------------------ + */ + $this->assertTrue(is_string($command->getFireMessage(true))); + $this->assertTrue(is_string($command->getFireMessage(false))); + } +}