From 7178c66cf5fb0e347bcad998d27057721b3d62ff Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 1 Jan 2016 21:46:55 +0000 Subject: [PATCH 01/11] Fix error with database migrate command --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b1570a1f4..eee4b5a6c 100644 --- a/readme.md +++ b/readme.md @@ -38,7 +38,7 @@ git clone https://github.com/ssddanbrown/BookStack.git --branch release --single 4. Ensure the `storage` & `bootstrap/cache` folders are writable by the web server. 5. In the application root, Run `php artisan key:generate` to generate a unique application key. 6. If not using apache or if `.htaccess` files are disabled you will have to create some URL rewrite rules as shown below. -7. Run `php migrate` to update the database. +7. Run `php artisan migrate` to update the database. 8. Done! You can now login using the default admin details `admin@admin.com` with a password of `password`. It is recommended to change these details directly after first logging in. #### URL Rewrite rules From 9319f99a3d084db2b6ad4df27327bf9e19c6269c Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 2 Jan 2016 15:02:19 +0000 Subject: [PATCH 02/11] Updated readme with installation clarification --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index eee4b5a6c..4073d171c 100644 --- a/readme.md +++ b/readme.md @@ -23,6 +23,8 @@ BookStack has similar requirements to Laravel. On top of those are some front-en Ensure the requirements are met before installing. +Currently BookStack requires its own domain/subdomain and will not work in a site subdirectory. + This project currently uses the `release` branch of this repository as a stable channel for providing updates. The installation is currently somewhat complicated and will be made simpler in future releases. Some PHP/Laravel experience will currently benefit. From e27a630a094639956d1a539531d722068fd4cde2 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 2 Jan 2016 16:24:09 +0000 Subject: [PATCH 03/11] Updated readme with social & update instructions --- .env.example | 8 +++---- app/Services/SocialAuthService.php | 2 +- readme.md | 37 +++++++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 91e59f966..00d230bff 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +7,7 @@ APP_KEY=SomeRandomString DB_HOST=localhost DB_DATABASE=database_database DB_USERNAME=database_username -DB_PASSWORD=database__user_password +DB_PASSWORD=database_user_password # Cache and session CACHE_DRIVER=file @@ -33,8 +33,8 @@ GOOGLE_APP_SECRET=false # URL used for social login redirects, NO TRAILING SLASH APP_URL=http://bookstack.dev -# External services -USE_GRAVATAR=true +# External services such as Gravatar +DISABLE_EXTERNAL_SERVICES=false # Mail settings MAIL_DRIVER=smtp @@ -42,4 +42,4 @@ MAIL_HOST=localhost MAIL_PORT=1025 MAIL_USERNAME=null MAIL_PASSWORD=null -MAIL_ENCRYPTION=null \ No newline at end of file +MAIL_ENCRYPTION=null diff --git a/app/Services/SocialAuthService.php b/app/Services/SocialAuthService.php index c96446ddf..15fd1c6a2 100644 --- a/app/Services/SocialAuthService.php +++ b/app/Services/SocialAuthService.php @@ -129,7 +129,7 @@ class SocialAuthService // When a user is logged in, A social account exists but the users do not match. // Change the user that the social account is assigned to. if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id != $currentUser->id) { - \Session::flash('success', 'This ' . title_case($socialDriver) . ' account is already used buy another user.'); + \Session::flash('success', 'This ' . title_case($socialDriver) . ' account is already used by another user.'); return redirect($currentUser->getEditUrl()); } diff --git a/readme.md b/readme.md index 4073d171c..1a429369b 100644 --- a/readme.md +++ b/readme.md @@ -61,6 +61,41 @@ location / { try_files $uri $uri/ /index.php?$query_string; } ``` +## Updating BookStack + +To update BookStack you can run the following command in the root directory of the application: +``` +git pull origin release && composer install && php artisan migrate +``` +This command will update the repository that was created in the installation, install the PHP dependencies using `composer` then run the database migrations. + +## Social Authentication + +BookStack currently supports login via both Google and Github. Once enabled options for these services will show up in the login, registration and user profile pages. By default these services are disabled. To enable them you will have to create an application on the external services to obtain the require application id's and secrets. Here are instructions to do this for the current supported services: + +### Google + +1. Open the [Google Developers Console](https://console.developers.google.com/). +2. Create a new project (May have to wait a short while for it to be created). +3. Select 'Enable and manage APIs'. +4. Enable the 'Google+ API'. +5. In 'Credentials' choose the 'OAuth consent screen' tab and enter a product name ('BookStack' or your custom set name). +6. Back in the 'Credentials' tab click 'New credentials' > 'OAuth client ID'. +7. Choose an application type of 'Web application' and enter the following urls under 'Authorized redirect URIs', changing `https://example.com` to your own domain where BookStack is hosted: + - `https://example.com/login/service/google/callback` + - `https://example.com/register/service/google/callback` +8. Click 'Create' and your app_id and secret will be displayed. Replace the false value on both the `GOOGLE_APP_ID` & `GOOGLE_APP_SECRET` variables in the '.env' file in the BookStack root directory with your own app_id and secret. +9. Set the 'APP_URL' environment variable to be the same domain as you entered in step 7. So, in this example, it will be `https://example.com`. +10. All done! Users should now be able to link to their social accounts in their account profile pages and also register/login using their Google accounts. + +### Github + +1. While logged in, open up your [GitHub developer applications](https://github.com/settings/developers). +2. Click 'Register new application'. +3. Enter an application name ('BookStack' or your custom set name), A link to your app instance under 'Homepage URL' and an 'Authorization callback URL' of the url that your BookStack instance is hosted on then click 'Register application'. +4. A 'Client ID' and a 'Client Secret' value will be shown. Add these two values to the to the `GITHUB_APP_ID` and `GITHUB_APP_SECRET` variables, replacing the default false value, in the '.env' file found in the BookStack root folder. +5. Set the 'APP_URL' environment variable to be the same domain as you entered in step 3. +6. All done! Users should now be able to link to their social accounts in their account profile pages and also register/login using their Github account. ## Testing @@ -73,7 +108,7 @@ php artisan migrate --database=mysql_testing php artisan db:seed --class=DummyContentSeeder --database=mysql_testing ``` -Once done you can run `phpunit` in the application root directory to run all tests. +Once done you can run `phpunit` (or `./vendor/bin/phpunit` if `phpunit` is not found) in the application root directory to run all tests. ## License From 14ca31768cdc79e06a4ab4c74bb650a37bff4eb5 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 9 Jan 2016 19:23:35 +0000 Subject: [PATCH 04/11] Updated laravel to 5.2 and started ldap implementation --- app/Exceptions/Handler.php | 6 + app/Exceptions/LdapException.php | 9 + app/Http/Controllers/Auth/AuthController.php | 11 +- app/Http/Controllers/Controller.php | 2 +- app/Http/Controllers/UserController.php | 2 +- app/Http/routes.php | 6 + app/Providers/AuthServiceProvider.php | 31 + app/Providers/LdapUserProvider.php | 117 ++ app/Services/ImageService.php | 6 +- app/Services/LdapService.php | 60 + app/Services/SocialAuthService.php | 13 +- composer.json | 6 +- composer.lock | 1019 ++++++++++------- config/app.php | 6 +- config/auth.php | 119 +- config/filesystems.php | 13 +- config/services.php | 10 + .../views/auth/forms/login/ldap.blade.php | 9 + .../views/auth/forms/login/standard.blade.php | 10 + resources/views/auth/login.blade.php | 12 +- 20 files changed, 950 insertions(+), 517 deletions(-) create mode 100644 app/Exceptions/LdapException.php create mode 100644 app/Providers/AuthServiceProvider.php create mode 100644 app/Providers/LdapUserProvider.php create mode 100644 app/Services/LdapService.php create mode 100644 resources/views/auth/forms/login/ldap.blade.php create mode 100644 resources/views/auth/forms/login/standard.blade.php diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 792bdd266..84f38e8f5 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -3,8 +3,11 @@ namespace BookStack\Exceptions; use Exception; +use Illuminate\Contracts\Validation\ValidationException; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Symfony\Component\HttpKernel\Exception\HttpException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Illuminate\Auth\Access\AuthorizationException; class Handler extends ExceptionHandler { @@ -14,7 +17,10 @@ class Handler extends ExceptionHandler * @var array */ protected $dontReport = [ + AuthorizationException::class, HttpException::class, + ModelNotFoundException::class, + ValidationException::class, ]; /** diff --git a/app/Exceptions/LdapException.php b/app/Exceptions/LdapException.php new file mode 100644 index 000000000..acdb24302 --- /dev/null +++ b/app/Exceptions/LdapException.php @@ -0,0 +1,9 @@ +exists('auth.authenticate')) { - return view('auth.authenticate'); - } - $socialDrivers = $this->socialAuthService->getActiveDrivers(); - return view('auth.login', ['socialDrivers' => $socialDrivers]); + $authMethod = 'standard'; // TODO - rewrite to use config. + return view('auth/login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]); } /** @@ -253,7 +248,7 @@ class AuthController extends Controller } /** - * Redirect to the social site for authentication initended to register. + * Redirect to the social site for authentication intended to register. * @param $socialDriver * @return mixed */ diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index ca022f7ca..ab37a44a1 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -48,7 +48,7 @@ abstract class Controller extends BaseController */ protected function preventAccessForDemoUsers() { - if (env('APP_ENV', 'production') === 'demo') $this->showPermissionError(); + if (config('app.env') === 'demo') $this->showPermissionError(); } /** diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index b81be16f6..9184b245e 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -72,7 +72,7 @@ class UserController extends Controller $user->attachRoleId($request->get('role')); // Get avatar from gravatar and save - if (!env('DISABLE_EXTERNAL_SERVICES', false)) { + if (!config('services.disable_services')) { $avatar = \Images::saveUserGravatar($user); $user->avatar()->associate($avatar); $user->save(); diff --git a/app/Http/routes.php b/app/Http/routes.php index 23d4c33ab..a8eb6b6e4 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -1,5 +1,11 @@ getUserDetails('ssmith'); +}); + // Authenticated routes... Route::group(['middleware' => 'auth'], function () { diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php new file mode 100644 index 000000000..40e94b016 --- /dev/null +++ b/app/Providers/AuthServiceProvider.php @@ -0,0 +1,31 @@ +model = $model; + } + + /** + * Create a new instance of the model. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function createModel() + { + $class = '\\'.ltrim($this->model, '\\'); + + return new $class; + } + + + /** + * Retrieve a user by their unique identifier. + * + * @param mixed $identifier + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function retrieveById($identifier) + { + return $this->createModel()->newQuery()->find($identifier); + } + + /** + * Retrieve a user by their unique identifier and "remember me" token. + * + * @param mixed $identifier + * @param string $token + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function retrieveByToken($identifier, $token) + { + $model = $this->createModel(); + + return $model->newQuery() + ->where($model->getAuthIdentifierName(), $identifier) + ->where($model->getRememberTokenName(), $token) + ->first(); + } + + + /** + * Update the "remember me" token for the given user in storage. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param string $token + * @return void + */ + public function updateRememberToken(Authenticatable $user, $token) + { + $user->setRememberToken($token); + + $user->save(); + } + + /** + * Retrieve a user by the given credentials. + * + * @param array $credentials + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function retrieveByCredentials(array $credentials) + { + // TODO: Implement retrieveByCredentials() method. + + // Get user via LDAP + + // Search current user base by looking up a uid + + // If not exists create a new user instance with attached role + // but do not store it in the database yet + + // + } + + /** + * Validate a user against the given credentials. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param array $credentials + * @return bool + */ + public function validateCredentials(Authenticatable $user, array $credentials) + { + // TODO: Implement validateCredentials() method. + } +} diff --git a/app/Services/ImageService.php b/app/Services/ImageService.php index 3f3094103..600c85a24 100644 --- a/app/Services/ImageService.php +++ b/app/Services/ImageService.php @@ -200,7 +200,7 @@ class ImageService { if ($this->storageInstance !== null) return $this->storageInstance; - $storageType = env('STORAGE_TYPE'); + $storageType = config('filesystems.default'); $this->storageInstance = $this->fileSystem->disk($storageType); return $this->storageInstance; @@ -226,10 +226,10 @@ class ImageService private function getPublicUrl($filePath) { if ($this->storageUrl === null) { - $storageUrl = env('STORAGE_URL'); + $storageUrl = config('filesystems.url'); // Get the standard public s3 url if s3 is set as storage type - if ($storageUrl == false && env('STORAGE_TYPE') === 's3') { + if ($storageUrl == false && config('filesystems.default') === 's3') { $storageDetails = config('filesystems.disks.s3'); $storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket']; } diff --git a/app/Services/LdapService.php b/app/Services/LdapService.php new file mode 100644 index 000000000..a540ab58b --- /dev/null +++ b/app/Services/LdapService.php @@ -0,0 +1,60 @@ + 1 ? $ldapServer[1] : 389); + + if ($ldapConnection === false) { + throw new LdapException('Cannot connect to ldap server, Initial connection failed'); + } + + // Options + + ldap_set_option($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3); // TODO - make configurable + + $ldapDn = config('services.ldap.dn'); + $ldapPass = config('services.ldap.pass'); + $isAnonymous = ($ldapDn === false || $ldapPass === false); + if ($isAnonymous) { + $ldapBind = ldap_bind($ldapConnection); + } else { + $ldapBind = ldap_bind($ldapConnection, $ldapDn, $ldapPass); + } + + if (!$ldapBind) throw new LdapException('LDAP access failed using ' . $isAnonymous ? ' anonymous bind.' : ' given dn & pass details'); + + // Find user + $userFilter = $this->buildFilter(config('services.ldap.user_filter'), ['user' => $userName]); + //dd($userFilter); + $baseDn = config('services.ldap.base_dn'); + $ldapSearch = ldap_search($ldapConnection, $baseDn, $userFilter); + $users = ldap_get_entries($ldapConnection, $ldapSearch); + + dd($users); + } + + + private function buildFilter($filterString, $attrs) + { + $newAttrs = []; + foreach ($attrs as $key => $attrText) { + $newKey = '${'.$key.'}'; + $newAttrs[$newKey] = $attrText; + } + return strtr($filterString, $newAttrs); + } + +} \ No newline at end of file diff --git a/app/Services/SocialAuthService.php b/app/Services/SocialAuthService.php index 15fd1c6a2..2437a4827 100644 --- a/app/Services/SocialAuthService.php +++ b/app/Services/SocialAuthService.php @@ -76,9 +76,9 @@ class SocialAuthService throw new UserRegistrationException('This ' . $socialDriver . ' account is already in use, Try logging in via the ' . $socialDriver . ' option.', '/login'); } - if($this->userRepo->getByEmail($socialUser->getEmail())) { + if ($this->userRepo->getByEmail($socialUser->getEmail())) { $email = $socialUser->getEmail(); - throw new UserRegistrationException('The email '. $email.' is already in use. If you already have an account you can connect your ' . $socialDriver .' account from your profile settings.', '/login'); + throw new UserRegistrationException('The email ' . $email . ' is already in use. If you already have an account you can connect your ' . $socialDriver . ' account from your profile settings.', '/login'); } return $socialUser; @@ -172,9 +172,10 @@ class SocialAuthService */ private function checkDriverConfigured($driver) { - $upperName = strtoupper($driver); - $config = [env($upperName . '_APP_ID', false), env($upperName . '_APP_SECRET', false), env('APP_URL', false)]; - return (!in_array(false, $config) && !in_array(null, $config)); + $lowerName = strtolower($driver); + $configPrefix = 'services.' . $lowerName . '.'; + $config = [config($configPrefix . 'client_id'), config($configPrefix . 'client_secret'), config('services.callback_url')]; + return !in_array(false, $config) && !in_array(null, $config); } /** @@ -193,7 +194,7 @@ class SocialAuthService } /** - * @param string $socialDriver + * @param string $socialDriver * @param \Laravel\Socialite\Contracts\User $socialUser * @return SocialAccount */ diff --git a/composer.json b/composer.json index 748ff68f7..0f6cec080 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "type": "project", "require": { "php": ">=5.5.9", - "laravel/framework": "5.1.*", + "laravel/framework": "5.2.*", "intervention/image": "^2.3", "laravel/socialite": "^2.0", "barryvdh/laravel-ide-helper": "^2.1", @@ -17,7 +17,9 @@ "fzaninotto/faker": "~1.4", "mockery/mockery": "0.9.*", "phpunit/phpunit": "~4.0", - "phpspec/phpspec": "~2.1" + "phpspec/phpspec": "~2.1", + "symfony/dom-crawler": "~3.0", + "symfony/css-selector": "~3.0" }, "autoload": { "classmap": [ diff --git a/composer.lock b/composer.lock index 23d2d50b6..364e7092e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "19725116631f01881caafa33052eecb9", - "content-hash": "f1dbd776f0ae13ec99e4e6d99510cd8e", + "hash": "1ca2bc3308d193a556124513e1f57106", + "content-hash": "99d01bead4e1ead29f826cd7eae234ea", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.11.4", + "version": "3.12.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "2524c78e0fa1ed049719b8b6b0696f0b6dfb1ca2" + "reference": "5ee0f33fafe47740c03ff38ddb73ae4f52b4da5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2524c78e0fa1ed049719b8b6b0696f0b6dfb1ca2", - "reference": "2524c78e0fa1ed049719b8b6b0696f0b6dfb1ca2", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5ee0f33fafe47740c03ff38ddb73ae4f52b4da5b", + "reference": "5ee0f33fafe47740c03ff38ddb73ae4f52b4da5b", "shasum": "" }, "require": { @@ -43,6 +43,7 @@ "phpunit/phpunit": "~4.0" }, "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", "doctrine/cache": "To use the DoctrineCacheAdapter", "ext-curl": "To send requests using cURL", "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages" @@ -83,32 +84,32 @@ "s3", "sdk" ], - "time": "2015-12-04 01:19:53" + "time": "2016-01-06 22:50:48" }, { "name": "barryvdh/laravel-debugbar", - "version": "v2.0.6", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "23672cbbe78278ff1fdb258caa0b1de7b5d0bc0c" + "reference": "974fd16e328ca851a081449100d9509af59cf0ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/23672cbbe78278ff1fdb258caa0b1de7b5d0bc0c", - "reference": "23672cbbe78278ff1fdb258caa0b1de7b5d0bc0c", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/974fd16e328ca851a081449100d9509af59cf0ff", + "reference": "974fd16e328ca851a081449100d9509af59cf0ff", "shasum": "" }, "require": { - "illuminate/support": "~5.0.17|5.1.*", - "maximebf/debugbar": "~1.10.2", + "illuminate/support": "~5.0.17|5.1.*|5.2.*", + "maximebf/debugbar": "~1.11.0", "php": ">=5.4.0", - "symfony/finder": "~2.6" + "symfony/finder": "~2.6|~3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" } }, "autoload": { @@ -137,26 +138,26 @@ "profiler", "webprofiler" ], - "time": "2015-09-09 11:39:27" + "time": "2015-12-22 06:22:38" }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.1.0", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "83999f8467374adcb8893f566c9171c9d9691f50" + "reference": "d82e8f191fb043a0f8cbf2de64fd3027bfa4f772" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/83999f8467374adcb8893f566c9171c9d9691f50", - "reference": "83999f8467374adcb8893f566c9171c9d9691f50", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/d82e8f191fb043a0f8cbf2de64fd3027bfa4f772", + "reference": "d82e8f191fb043a0f8cbf2de64fd3027bfa4f772", "shasum": "" }, "require": { - "illuminate/console": "5.0.x|5.1.x", - "illuminate/filesystem": "5.0.x|5.1.x", - "illuminate/support": "5.0.x|5.1.x", + "illuminate/console": "5.0.x|5.1.x|5.2.x", + "illuminate/filesystem": "5.0.x|5.1.x|5.2.x", + "illuminate/support": "5.0.x|5.1.x|5.2.x", "php": ">=5.4.0", "phpdocumentor/reflection-docblock": "2.0.4", "symfony/class-loader": "~2.3" @@ -200,7 +201,7 @@ "phpstorm", "sublime" ], - "time": "2015-08-13 11:40:00" + "time": "2015-12-21 19:48:06" }, { "name": "classpreloader/classpreloader", @@ -256,62 +257,6 @@ ], "time": "2015-11-09 22:51:51" }, - { - "name": "danielstjules/stringy", - "version": "1.10.0", - "source": { - "type": "git", - "url": "https://github.com/danielstjules/Stringy.git", - "reference": "4749c205db47ee5b32e8d1adf6d9aff8db6caf3b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/danielstjules/Stringy/zipball/4749c205db47ee5b32e8d1adf6d9aff8db6caf3b", - "reference": "4749c205db47ee5b32e8d1adf6d9aff8db6caf3b", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Stringy\\": "src/" - }, - "files": [ - "src/Create.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Daniel St. Jules", - "email": "danielst.jules@gmail.com", - "homepage": "http://www.danielstjules.com" - } - ], - "description": "A string manipulation library with multibyte support", - "homepage": "https://github.com/danielstjules/Stringy", - "keywords": [ - "UTF", - "helpers", - "manipulation", - "methods", - "multibyte", - "string", - "utf-8", - "utility", - "utils" - ], - "time": "2015-07-23 00:54:12" - }, { "name": "dnoegel/php-xdg-base-dir", "version": "0.1", @@ -414,22 +359,22 @@ }, { "name": "guzzle/guzzle", - "version": "v3.9.3", + "version": "v3.8.1", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle3.git", - "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + "url": "https://github.com/guzzle/guzzle.git", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", - "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", "shasum": "" }, "require": { "ext-curl": "*", "php": ">=5.3.3", - "symfony/event-dispatcher": "~2.1" + "symfony/event-dispatcher": ">=2.1" }, "replace": { "guzzle/batch": "self.version", @@ -456,21 +401,18 @@ "guzzle/stream": "self.version" }, "require-dev": { - "doctrine/cache": "~1.3", - "monolog/monolog": "~1.0", + "doctrine/cache": "*", + "monolog/monolog": "1.*", "phpunit/phpunit": "3.7.*", - "psr/log": "~1.0", - "symfony/class-loader": "~2.1", - "zendframework/zend-cache": "2.*,<2.3", - "zendframework/zend-log": "2.*,<2.3" - }, - "suggest": { - "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + "psr/log": "1.0.*", + "symfony/class-loader": "*", + "zendframework/zend-cache": "<2.3", + "zendframework/zend-log": "<2.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.9-dev" + "dev-master": "3.8-dev" } }, "autoload": { @@ -494,7 +436,7 @@ "homepage": "https://github.com/guzzle/guzzle/contributors" } ], - "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", "homepage": "http://guzzlephp.org/", "keywords": [ "client", @@ -505,7 +447,7 @@ "rest", "web service" ], - "time": "2015-03-18 18:23:50" + "time": "2014-01-28 22:29:15" }, { "name": "guzzlehttp/guzzle", @@ -680,16 +622,16 @@ }, { "name": "intervention/image", - "version": "2.3.4", + "version": "2.3.5", "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "a67ee32df0c6820cc6e861ad4144ee0ef9c74aa3" + "reference": "9f29360b8ab94585cb9e80cf9045abd5b85feb89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/a67ee32df0c6820cc6e861ad4144ee0ef9c74aa3", - "reference": "a67ee32df0c6820cc6e861ad4144ee0ef9c74aa3", + "url": "https://api.github.com/repos/Intervention/image/zipball/9f29360b8ab94585cb9e80cf9045abd5b85feb89", + "reference": "9f29360b8ab94585cb9e80cf9045abd5b85feb89", "shasum": "" }, "require": { @@ -738,7 +680,7 @@ "thumbnail", "watermark" ], - "time": "2015-11-30 17:03:21" + "time": "2016-01-02 19:15:13" }, { "name": "jakub-onderka/php-console-color", @@ -829,30 +771,30 @@ }, { "name": "jeremeamia/SuperClosure", - "version": "2.1.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/jeremeamia/super_closure.git", - "reference": "b712f39c671e5ead60c7ebfe662545456aade833" + "reference": "29a88be2a4846d27c1613aed0c9071dfad7b5938" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/b712f39c671e5ead60c7ebfe662545456aade833", - "reference": "b712f39c671e5ead60c7ebfe662545456aade833", + "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/29a88be2a4846d27c1613aed0c9071dfad7b5938", + "reference": "29a88be2a4846d27c1613aed0c9071dfad7b5938", "shasum": "" }, "require": { - "nikic/php-parser": "~1.0", - "php": ">=5.4" + "nikic/php-parser": "^1.2|^2.0", + "php": ">=5.4", + "symfony/polyfill-php56": "^1.0" }, "require-dev": { - "codeclimate/php-test-reporter": "~0.1.2", - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^4.0|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" } }, "autoload": { @@ -869,7 +811,7 @@ "name": "Jeremy Lindblom", "email": "jeremeamia@gmail.com", "homepage": "https://github.com/jeremeamia", - "role": "developer" + "role": "Developer" } ], "description": "Serialize Closure objects, including their context and binding", @@ -883,49 +825,47 @@ "serialize", "tokenizer" ], - "time": "2015-03-11 20:06:43" + "time": "2015-12-05 17:17:57" }, { "name": "laravel/framework", - "version": "v5.1.25", + "version": "v5.2.7", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "53979acc664debc401bfcb61086c4fc4f196b22d" + "reference": "26cd65eaa4bcc0fb0be381cfb7cfdcda06a3c2b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/53979acc664debc401bfcb61086c4fc4f196b22d", - "reference": "53979acc664debc401bfcb61086c4fc4f196b22d", + "url": "https://api.github.com/repos/laravel/framework/zipball/26cd65eaa4bcc0fb0be381cfb7cfdcda06a3c2b4", + "reference": "26cd65eaa4bcc0fb0be381cfb7cfdcda06a3c2b4", "shasum": "" }, "require": { - "classpreloader/classpreloader": "~2.0|~3.0", - "danielstjules/stringy": "~1.8", + "classpreloader/classpreloader": "~3.0", "doctrine/inflector": "~1.0", "ext-mbstring": "*", "ext-openssl": "*", - "jeremeamia/superclosure": "~2.0", + "jeremeamia/superclosure": "~2.2", "league/flysystem": "~1.0", "monolog/monolog": "~1.11", "mtdowling/cron-expression": "~1.0", - "nesbot/carbon": "~1.19", + "nesbot/carbon": "~1.20", "paragonie/random_compat": "~1.1", "php": ">=5.5.9", "psy/psysh": "0.6.*", "swiftmailer/swiftmailer": "~5.1", - "symfony/console": "2.7.*", - "symfony/css-selector": "2.7.*", - "symfony/debug": "2.7.*", - "symfony/dom-crawler": "2.7.*", - "symfony/finder": "2.7.*", - "symfony/http-foundation": "2.7.*", - "symfony/http-kernel": "2.7.*", - "symfony/process": "2.7.*", - "symfony/routing": "2.7.*", - "symfony/translation": "2.7.*", - "symfony/var-dumper": "2.7.*", - "vlucas/phpdotenv": "~1.0" + "symfony/console": "2.8.*|3.0.*", + "symfony/debug": "2.8.*|3.0.*", + "symfony/finder": "2.8.*|3.0.*", + "symfony/http-foundation": "2.8.*|3.0.*", + "symfony/http-kernel": "2.8.*|3.0.*", + "symfony/polyfill-php56": "~1.0", + "symfony/process": "2.8.*|3.0.*", + "symfony/routing": "2.8.*|3.0.*", + "symfony/translation": "2.8.*|3.0.*", + "symfony/var-dumper": "2.8.*|3.0.*", + "vlucas/phpdotenv": "~2.2" }, "replace": { "illuminate/auth": "self.version", @@ -942,7 +882,6 @@ "illuminate/events": "self.version", "illuminate/exception": "self.version", "illuminate/filesystem": "self.version", - "illuminate/foundation": "self.version", "illuminate/hashing": "self.version", "illuminate/http": "self.version", "illuminate/log": "self.version", @@ -960,28 +899,30 @@ }, "require-dev": { "aws/aws-sdk-php": "~3.0", - "iron-io/iron_mq": "~2.0", "mockery/mockery": "~0.9.2", "pda/pheanstalk": "~3.0", - "phpunit/phpunit": "~4.0", - "predis/predis": "~1.0" + "phpunit/phpunit": "~4.1", + "predis/predis": "~1.0", + "symfony/css-selector": "2.8.*|3.0.*", + "symfony/dom-crawler": "2.8.*|3.0.*" }, "suggest": { "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.4).", "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", - "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers (~5.3|~6.0).", - "iron-io/iron_mq": "Required to use the iron queue driver (~2.0).", + "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~5.3|~6.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).", "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).", "predis/predis": "Required to use the redis cache and queue drivers (~1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0)." + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).", + "symfony/css-selector": "Required to use some of the crawler integration testing tools (2.8.*|3.0.*).", + "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (2.8.*|3.0.*)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-master": "5.2-dev" } }, "autoload": { @@ -1012,7 +953,7 @@ "framework", "laravel" ], - "time": "2015-11-30 19:24:36" + "time": "2016-01-07 13:54:34" }, { "name": "laravel/socialite", @@ -1070,16 +1011,16 @@ }, { "name": "league/flysystem", - "version": "1.0.15", + "version": "1.0.16", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "31525caf9e8772683672fefd8a1ca0c0736020f4" + "reference": "183e1a610664baf6dcd6fceda415baf43cbdc031" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/31525caf9e8772683672fefd8a1ca0c0736020f4", - "reference": "31525caf9e8772683672fefd8a1ca0c0736020f4", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/183e1a610664baf6dcd6fceda415baf43cbdc031", + "reference": "183e1a610664baf6dcd6fceda415baf43cbdc031", "shasum": "" }, "require": { @@ -1093,7 +1034,7 @@ "mockery/mockery": "~0.9", "phpspec/phpspec": "^2.2", "phpspec/prophecy-phpunit": "~1.0", - "phpunit/phpunit": "~4.1" + "phpunit/phpunit": "~4.8" }, "suggest": { "ext-fileinfo": "Required for MimeType", @@ -1150,7 +1091,7 @@ "sftp", "storage" ], - "time": "2015-09-30 22:26:59" + "time": "2015-12-19 20:16:43" }, { "name": "league/flysystem-aws-s3-v3", @@ -1264,25 +1205,25 @@ }, { "name": "maximebf/debugbar", - "version": "v1.10.5", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "30e53e8a28284b69dd223c9f5ee8957befd72636" + "reference": "07741d84d39d10f00551c94284cdefcc69703e77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/30e53e8a28284b69dd223c9f5ee8957befd72636", - "reference": "30e53e8a28284b69dd223c9f5ee8957befd72636", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/07741d84d39d10f00551c94284cdefcc69703e77", + "reference": "07741d84d39d10f00551c94284cdefcc69703e77", "shasum": "" }, "require": { "php": ">=5.3.0", - "psr/log": "~1.0", - "symfony/var-dumper": "~2.6" + "psr/log": "^1.0", + "symfony/var-dumper": "^2.6|^3.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^4.0|^5.0" }, "suggest": { "kriswallsmith/assetic": "The best way to manage assets", @@ -1292,12 +1233,12 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10-dev" + "dev-master": "1.11-dev" } }, "autoload": { - "psr-0": { - "DebugBar": "src/" + "psr-4": { + "DebugBar\\": "src/DebugBar/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1309,14 +1250,19 @@ "name": "Maxime Bouroumeau-Fuseau", "email": "maxime.bouroumeau@gmail.com", "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" } ], "description": "Debug bar in the browser for php application", "homepage": "https://github.com/maximebf/php-debugbar", "keywords": [ - "debug" + "debug", + "debugbar" ], - "time": "2015-10-19 20:35:12" + "time": "2015-12-10 09:50:24" }, { "name": "monolog/monolog", @@ -1441,16 +1387,16 @@ }, { "name": "mtdowling/jmespath.php", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "a7d99d0c836e69d27b7bfca1d33ca2759fba3289" + "reference": "192f93e43c2c97acde7694993ab171b3de284093" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a7d99d0c836e69d27b7bfca1d33ca2759fba3289", - "reference": "a7d99d0c836e69d27b7bfca1d33ca2759fba3289", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/192f93e43c2c97acde7694993ab171b3de284093", + "reference": "192f93e43c2c97acde7694993ab171b3de284093", "shasum": "" }, "require": { @@ -1492,7 +1438,7 @@ "json", "jsonpath" ], - "time": "2015-05-27 17:21:31" + "time": "2016-01-05 18:25:05" }, { "name": "nesbot/carbon", @@ -1543,32 +1489,38 @@ }, { "name": "nikic/php-parser", - "version": "v1.4.1", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51" + "reference": "c542e5d86a9775abd1021618eb2430278bfc1e01" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", - "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c542e5d86a9775abd1021618eb2430278bfc1e01", + "reference": "c542e5d86a9775abd1021618eb2430278bfc1e01", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=5.3" + "php": ">=5.4" }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "bin/php-parse" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { - "files": [ - "lib/bootstrap.php" - ] + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1584,20 +1536,20 @@ "parser", "php" ], - "time": "2015-09-19 14:15:08" + "time": "2015-12-04 15:28:43" }, { "name": "paragonie/random_compat", - "version": "1.1.1", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "a208865a5aeffc2dbbef2a5b3409887272d93f32" + "reference": "dd8998b7c846f6909f4e7a5f67fabebfc412a4f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/a208865a5aeffc2dbbef2a5b3409887272d93f32", - "reference": "a208865a5aeffc2dbbef2a5b3409887272d93f32", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/dd8998b7c846f6909f4e7a5f67fabebfc412a4f7", + "reference": "dd8998b7c846f6909f4e7a5f67fabebfc412a4f7", "shasum": "" }, "require": { @@ -1632,7 +1584,7 @@ "pseudorandom", "random" ], - "time": "2015-12-01 02:52:15" + "time": "2016-01-06 13:31:20" }, { "name": "phpdocumentor/reflection-docblock", @@ -1897,16 +1849,16 @@ }, { "name": "symfony/class-loader", - "version": "v2.8.0", + "version": "v2.8.1", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "51f83451bf0ddfc696e47e4642d6cd10fcfce160" + "reference": "ec74b0a279cf3a9bd36172b3e3061591d380ce6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/51f83451bf0ddfc696e47e4642d6cd10fcfce160", - "reference": "51f83451bf0ddfc696e47e4642d6cd10fcfce160", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/ec74b0a279cf3a9bd36172b3e3061591d380ce6c", + "reference": "ec74b0a279cf3a9bd36172b3e3061591d380ce6c", "shasum": "" }, "require": { @@ -1945,29 +1897,30 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2015-11-26 07:00:59" + "time": "2015-12-05 17:37:59" }, { "name": "symfony/console", - "version": "v2.7.7", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "16bb1cb86df43c90931df65f529e7ebd79636750" + "reference": "ebcdc507829df915f4ca23067bd59ee4ef61f6c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/16bb1cb86df43c90931df65f529e7ebd79636750", - "reference": "16bb1cb86df43c90931df65f529e7ebd79636750", + "url": "https://api.github.com/repos/symfony/console/zipball/ebcdc507829df915f4ca23067bd59ee4ef61f6c3", + "reference": "ebcdc507829df915f4ca23067bd59ee4ef61f6c3", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/event-dispatcher": "~2.1", - "symfony/process": "~2.1" + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" }, "suggest": { "psr/log": "For using the console logger", @@ -1977,7 +1930,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2004,90 +1957,37 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2015-11-18 09:54:26" - }, - { - "name": "symfony/css-selector", - "version": "v2.7.7", - "source": { - "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "abb47717fb88aebd9437da2fc8bb01a50a36679f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/abb47717fb88aebd9437da2fc8bb01a50a36679f", - "reference": "abb47717fb88aebd9437da2fc8bb01a50a36679f", - "shasum": "" - }, - "require": { - "php": ">=5.3.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\CssSelector\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony CssSelector Component", - "homepage": "https://symfony.com", - "time": "2015-10-30 20:10:21" + "time": "2015-12-22 10:39:06" }, { "name": "symfony/debug", - "version": "v2.7.7", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "0dbc119596f4afc82d9b2eb2a7e6a4af1ee763fa" + "reference": "73612266ac709769effdbfc0762e5b07cfd2ac2a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/0dbc119596f4afc82d9b2eb2a7e6a4af1ee763fa", - "reference": "0dbc119596f4afc82d9b2eb2a7e6a4af1ee763fa", + "url": "https://api.github.com/repos/symfony/debug/zipball/73612266ac709769effdbfc0762e5b07cfd2ac2a", + "reference": "73612266ac709769effdbfc0762e5b07cfd2ac2a", "shasum": "" }, "require": { - "php": ">=5.3.9", + "php": ">=5.5.9", "psr/log": "~1.0" }, "conflict": { "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" }, "require-dev": { - "symfony/class-loader": "~2.2", - "symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2" + "symfony/class-loader": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2114,86 +2014,31 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2015-10-30 20:10:21" - }, - { - "name": "symfony/dom-crawler", - "version": "v2.7.7", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "b33593cbfe1d81b50d48353f338aca76a08658d8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b33593cbfe1d81b50d48353f338aca76a08658d8", - "reference": "b33593cbfe1d81b50d48353f338aca76a08658d8", - "shasum": "" - }, - "require": { - "php": ">=5.3.9" - }, - "require-dev": { - "symfony/css-selector": "~2.3" - }, - "suggest": { - "symfony/css-selector": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony DomCrawler Component", - "homepage": "https://symfony.com", - "time": "2015-11-02 20:20:53" + "time": "2015-12-26 13:39:53" }, { "name": "symfony/event-dispatcher", - "version": "v2.8.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc" + "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc", - "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d36355e026905fa5229e1ed7b4e9eda2e67adfcf", + "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.0,>=2.0.5|~3.0.0", - "symfony/dependency-injection": "~2.6|~3.0.0", - "symfony/expression-language": "~2.6|~3.0.0", - "symfony/stopwatch": "~2.3|~3.0.0" + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" }, "suggest": { "symfony/dependency-injection": "", @@ -2202,7 +2047,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2229,29 +2074,29 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2015-10-30 20:15:42" + "time": "2015-10-30 23:35:59" }, { "name": "symfony/finder", - "version": "v2.7.7", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "a06a0c0ff7db3736a50d530c908cca547bf13da9" + "reference": "8617895eb798b6bdb338321ce19453dc113e5675" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/a06a0c0ff7db3736a50d530c908cca547bf13da9", - "reference": "a06a0c0ff7db3736a50d530c908cca547bf13da9", + "url": "https://api.github.com/repos/symfony/finder/zipball/8617895eb798b6bdb338321ce19453dc113e5675", + "reference": "8617895eb798b6bdb338321ce19453dc113e5675", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2278,41 +2123,38 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2015-10-30 20:10:21" + "time": "2015-12-05 11:13:14" }, { "name": "symfony/http-foundation", - "version": "v2.7.7", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "e83a3d105ddaf5a113e803c904fdec552d1f1c35" + "reference": "939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e83a3d105ddaf5a113e803c904fdec552d1f1c35", - "reference": "e83a3d105ddaf5a113e803c904fdec552d1f1c35", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5", + "reference": "939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9" }, "require-dev": { - "symfony/expression-language": "~2.4" + "symfony/expression-language": "~2.8|~3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "3.0-dev" } }, "autoload": { "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, - "classmap": [ - "Resources/stubs" - ], "exclude-from-classmap": [ "/Tests/" ] @@ -2333,48 +2175,48 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2015-11-20 17:41:18" + "time": "2015-12-18 15:43:53" }, { "name": "symfony/http-kernel", - "version": "v2.7.7", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "5570de31e8fbc03777a8c61eb24f9b626e5e5941" + "reference": "f7933e9f19e26e7baba7ec04735b466fedd3a6db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/5570de31e8fbc03777a8c61eb24f9b626e5e5941", - "reference": "5570de31e8fbc03777a8c61eb24f9b626e5e5941", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f7933e9f19e26e7baba7ec04735b466fedd3a6db", + "reference": "f7933e9f19e26e7baba7ec04735b466fedd3a6db", "shasum": "" }, "require": { - "php": ">=5.3.9", + "php": ">=5.5.9", "psr/log": "~1.0", - "symfony/debug": "~2.6,>=2.6.2", - "symfony/event-dispatcher": "~2.6,>=2.6.7", - "symfony/http-foundation": "~2.5,>=2.5.4" + "symfony/debug": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0" }, "conflict": { - "symfony/config": "<2.7" + "symfony/config": "<2.8" }, "require-dev": { - "symfony/browser-kit": "~2.3", - "symfony/class-loader": "~2.1", - "symfony/config": "~2.7", - "symfony/console": "~2.3", - "symfony/css-selector": "~2.0,>=2.0.5", - "symfony/dependency-injection": "~2.2", - "symfony/dom-crawler": "~2.0,>=2.0.5", - "symfony/expression-language": "~2.4", - "symfony/finder": "~2.0,>=2.0.5", - "symfony/process": "~2.0,>=2.0.5", - "symfony/routing": "~2.2", - "symfony/stopwatch": "~2.3", - "symfony/templating": "~2.2", - "symfony/translation": "~2.0,>=2.0.5", - "symfony/var-dumper": "~2.6" + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~2.8|~3.0" }, "suggest": { "symfony/browser-kit": "", @@ -2388,7 +2230,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2415,29 +2257,196 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2015-11-23 11:57:49" + "time": "2015-12-26 16:46:13" }, { - "name": "symfony/process", - "version": "v2.7.7", + "name": "symfony/polyfill-mbstring", + "version": "v1.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "f6290983c8725d0afa29bdc3e5295879de3e58f5" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f6290983c8725d0afa29bdc3e5295879de3e58f5", - "reference": "f6290983c8725d0afa29bdc3e5295879de3e58f5", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25", + "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2015-11-20 09:19:13" + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "e2e77609a9e2328eb370fbb0e0d8b2000ebb488f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/e2e77609a9e2328eb370fbb0e0d8b2000ebb488f", + "reference": "e2e77609a9e2328eb370fbb0e0d8b2000ebb488f", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2015-12-18 15:10:25" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "4271c55cbc0a77b2641f861b978123e46b3da969" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4271c55cbc0a77b2641f861b978123e46b3da969", + "reference": "4271c55cbc0a77b2641f861b978123e46b3da969", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2015-11-04 20:28:58" + }, + { + "name": "symfony/process", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "f4794f1d00f0746621be3020ffbd8c5e0b217ee3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/f4794f1d00f0746621be3020ffbd8c5e0b217ee3", + "reference": "f4794f1d00f0746621be3020ffbd8c5e0b217ee3", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" } }, "autoload": { @@ -2464,47 +2473,48 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2015-11-19 16:11:24" + "time": "2015-12-23 11:04:02" }, { "name": "symfony/routing", - "version": "v2.7.7", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "7450f6196711b124fb8b04a12286d01a0401ddfe" + "reference": "3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/7450f6196711b124fb8b04a12286d01a0401ddfe", - "reference": "7450f6196711b124fb8b04a12286d01a0401ddfe", + "url": "https://api.github.com/repos/symfony/routing/zipball/3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59", + "reference": "3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9" }, "conflict": { - "symfony/config": "<2.7" + "symfony/config": "<2.8" }, "require-dev": { "doctrine/annotations": "~1.0", "doctrine/common": "~2.2", "psr/log": "~1.0", - "symfony/config": "~2.7", - "symfony/expression-language": "~2.4", - "symfony/http-foundation": "~2.3", - "symfony/yaml": "~2.0,>=2.0.5" + "symfony/config": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" }, "suggest": { "doctrine/annotations": "For using the annotation loader", "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", "symfony/expression-language": "For using expression matching", "symfony/yaml": "For using the YAML loader" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2537,33 +2547,34 @@ "uri", "url" ], - "time": "2015-11-18 13:41:01" + "time": "2015-12-23 08:00:11" }, { "name": "symfony/translation", - "version": "v2.7.7", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "e4ecb9c3ba1304eaf24de15c2d7a428101c1982f" + "reference": "dff0867826a7068d673801b7522f8e2634016ef9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/e4ecb9c3ba1304eaf24de15c2d7a428101c1982f", - "reference": "e4ecb9c3ba1304eaf24de15c2d7a428101c1982f", + "url": "https://api.github.com/repos/symfony/translation/zipball/dff0867826a7068d673801b7522f8e2634016ef9", + "reference": "dff0867826a7068d673801b7522f8e2634016ef9", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/config": "<2.7" + "symfony/config": "<2.8" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.7", - "symfony/intl": "~2.4", - "symfony/yaml": "~2.2" + "symfony/config": "~2.8|~3.0", + "symfony/intl": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" }, "suggest": { "psr/log": "To use logging capability in translator", @@ -2573,7 +2584,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2600,24 +2611,28 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2015-11-18 13:41:01" + "time": "2015-12-05 17:45:07" }, { "name": "symfony/var-dumper", - "version": "v2.7.7", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "72bcb27411780eaee9469729aace73c0d46fb2b8" + "reference": "87db8700deb12ba2b65e858f656a1f885530bcb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/72bcb27411780eaee9469729aace73c0d46fb2b8", - "reference": "72bcb27411780eaee9469729aace73c0d46fb2b8", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/87db8700deb12ba2b65e858f656a1f885530bcb0", + "reference": "87db8700deb12ba2b65e858f656a1f885530bcb0", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "twig/twig": "~1.20|~2.0" }, "suggest": { "ext-symfony_debug": "" @@ -2625,7 +2640,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2659,32 +2674,37 @@ "debug", "dump" ], - "time": "2015-11-18 13:41:01" + "time": "2015-12-05 11:13:14" }, { "name": "vlucas/phpdotenv", - "version": "v1.1.1", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "0cac554ce06277e33ddf9f0b7ade4b8bbf2af3fa" + "reference": "9caf304153dc2288e4970caec6f1f3b3bc205412" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/0cac554ce06277e33ddf9f0b7ade4b8bbf2af3fa", - "reference": "0cac554ce06277e33ddf9f0b7ade4b8bbf2af3fa", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/9caf304153dc2288e4970caec6f1f3b3bc205412", + "reference": "9caf304153dc2288e4970caec6f1f3b3bc205412", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": ">=5.3.9" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^4.8|^5.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, "autoload": { - "psr-0": { - "Dotenv": "src/" + "psr-4": { + "Dotenv\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2705,7 +2725,7 @@ "env", "environment" ], - "time": "2015-05-30 15:59:26" + "time": "2015-12-29 15:10:30" } ], "packages-dev": [ @@ -2961,16 +2981,16 @@ }, { "name": "phpspec/phpspec", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/phpspec/phpspec.git", - "reference": "1d3938e6d9ffb1bd4805ea8ddac62ea48767f358" + "reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/phpspec/zipball/1d3938e6d9ffb1bd4805ea8ddac62ea48767f358", - "reference": "1d3938e6d9ffb1bd4805ea8ddac62ea48767f358", + "url": "https://api.github.com/repos/phpspec/phpspec/zipball/5528ce1e93a1efa090c9404aba3395c329b4e6ed", + "reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed", "shasum": "" }, "require": { @@ -3035,7 +3055,7 @@ "testing", "tests" ], - "time": "2015-11-29 02:03:49" + "time": "2016-01-01 10:17:54" }, { "name": "phpspec/prophecy", @@ -3339,16 +3359,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.8.19", + "version": "4.8.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b2caaf8947aba5e002d42126723e9d69795f32b4" + "reference": "ea76b17bced0500a28098626b84eda12dbcf119c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b2caaf8947aba5e002d42126723e9d69795f32b4", - "reference": "b2caaf8947aba5e002d42126723e9d69795f32b4", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c", + "reference": "ea76b17bced0500a28098626b84eda12dbcf119c", "shasum": "" }, "require": { @@ -3407,7 +3427,7 @@ "testing", "xunit" ], - "time": "2015-11-30 08:18:59" + "time": "2015-12-12 07:45:58" }, { "name": "phpunit/phpunit-mock-objects", @@ -3531,28 +3551,28 @@ }, { "name": "sebastian/diff", - "version": "1.3.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3" + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3", - "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "~4.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -3575,11 +3595,11 @@ } ], "description": "Diff implementation", - "homepage": "http://www.github.com/sebastianbergmann/diff", + "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ "diff" ], - "time": "2015-02-22 15:13:53" + "time": "2015-12-08 07:14:41" }, { "name": "sebastian/environment", @@ -3750,16 +3770,16 @@ }, { "name": "sebastian/recursion-context", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "994d4a811bafe801fb06dccbee797863ba2792ba" + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba", - "reference": "994d4a811bafe801fb06dccbee797863ba2792ba", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", "shasum": "" }, "require": { @@ -3799,7 +3819,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-06-21 08:04:50" + "time": "2015-11-11 19:50:13" }, { "name": "sebastian/version", @@ -3837,17 +3857,126 @@ "time": "2015-06-21 13:59:46" }, { - "name": "symfony/yaml", - "version": "v3.0.0", + "name": "symfony/css-selector", + "version": "v3.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "177a015cb0e19ff4a49e0e2e2c5fc1c1bee07002" + "url": "https://github.com/symfony/css-selector.git", + "reference": "4613311fd46e146f506403ce2f8a0c71d402d2a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/177a015cb0e19ff4a49e0e2e2c5fc1c1bee07002", - "reference": "177a015cb0e19ff4a49e0e2e2c5fc1c1bee07002", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/4613311fd46e146f506403ce2f8a0c71d402d2a3", + "reference": "4613311fd46e146f506403ce2f8a0c71d402d2a3", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2015-12-05 17:45:07" + }, + { + "name": "symfony/dom-crawler", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d", + "reference": "7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "https://symfony.com", + "time": "2015-12-26 13:42:31" + }, + { + "name": "symfony/yaml", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "3df409958a646dad2bc5046c3fb671ee24a1a691" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/3df409958a646dad2bc5046c3fb671ee24a1a691", + "reference": "3df409958a646dad2bc5046c3fb671ee24a1a691", "shasum": "" }, "require": { @@ -3883,7 +4012,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-11-30 12:36:17" + "time": "2015-12-26 13:39:53" } ], "aliases": [], diff --git a/config/app.php b/config/app.php index b9c207632..65bafefc6 100644 --- a/config/app.php +++ b/config/app.php @@ -2,6 +2,9 @@ return [ + + 'env' => env('APP_ENV', 'production'), + /* |-------------------------------------------------------------------------- | Application Debug Mode @@ -113,13 +116,11 @@ return [ /* * Laravel Framework Service Providers... */ - Illuminate\Foundation\Providers\ArtisanServiceProvider::class, Illuminate\Auth\AuthServiceProvider::class, Illuminate\Broadcasting\BroadcastServiceProvider::class, Illuminate\Bus\BusServiceProvider::class, Illuminate\Cache\CacheServiceProvider::class, Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, - Illuminate\Routing\ControllerServiceProvider::class, Illuminate\Cookie\CookieServiceProvider::class, Illuminate\Database\DatabaseServiceProvider::class, Illuminate\Encryption\EncryptionServiceProvider::class, @@ -149,6 +150,7 @@ return [ /* * Application Service Providers... */ + BookStack\Providers\AuthServiceProvider::class, BookStack\Providers\AppServiceProvider::class, BookStack\Providers\EventServiceProvider::class, BookStack\Providers\RouteServiceProvider::class, diff --git a/config/auth.php b/config/auth.php index d4f58587d..55d434cdf 100644 --- a/config/auth.php +++ b/config/auth.php @@ -2,66 +2,109 @@ return [ + + 'method' => env('AUTH_METHOD', 'standard'), + /* |-------------------------------------------------------------------------- - | Default Authentication Driver + | Authentication Defaults |-------------------------------------------------------------------------- | - | This option controls the authentication driver that will be utilized. - | This driver manages the retrieval and authentication of the users - | attempting to get access to protected areas of your application. + | This option controls the default authentication "guard" and password + | reset options for your application. You may change these defaults + | as required, but they're a perfect start for most applications. + | + */ + + 'defaults' => [ + 'guard' => 'web', + 'passwords' => 'users', + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | here which uses session storage and the Eloquent user provider. + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | Supported: "session", "token" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + + 'api' => [ + 'driver' => 'token', + 'provider' => 'users', + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | If you have multiple user tables or models you may configure multiple + | sources which represent each model / table. These sources may then + | be assigned to any extra authentication guards you have defined. | | Supported: "database", "eloquent" | */ - 'driver' => 'eloquent', + 'providers' => [ + 'users' => [ + 'driver' => env('AUTH_METHOD', 'eloquent'), + 'model' => Bookstack\User::class, + ], + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], /* |-------------------------------------------------------------------------- - | Authentication Model - |-------------------------------------------------------------------------- - | - | When using the "Eloquent" authentication driver, we need to know which - | Eloquent model should be used to retrieve your users. Of course, it - | is often just the "User" model but you may use whatever you like. - | - */ - - 'model' => BookStack\User::class, - - /* - |-------------------------------------------------------------------------- - | Authentication Table - |-------------------------------------------------------------------------- - | - | When using the "Database" authentication driver, we need to know which - | table should be used to retrieve your users. We have chosen a basic - | default value but you may easily change it to any table you like. - | - */ - - 'table' => 'users', - - /* - |-------------------------------------------------------------------------- - | Password Reset Settings + | Resetting Passwords |-------------------------------------------------------------------------- | | Here you may set the options for resetting passwords including the view - | that is your password reset e-mail. You can also set the name of the + | that is your password reset e-mail. You may also set the name of the | table that maintains all of the reset tokens for your application. | + | You may specify multiple password reset configurations if you have more + | than one user table or model in the application and you want to have + | separate password reset settings based on the specific user types. + | | The expire time is the number of minutes that the reset token should be | considered valid. This security feature keeps tokens short-lived so | they have less time to be guessed. You may change this as needed. | */ - 'password' => [ - 'email' => 'emails.password', - 'table' => 'password_resets', - 'expire' => 60, + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'email' => 'emails.password', + 'table' => 'password_resets', + 'expire' => 60, + ], ], -]; +]; \ No newline at end of file diff --git a/config/filesystems.php b/config/filesystems.php index 5fd3df6df..dbcb03db1 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -15,7 +15,18 @@ return [ | */ - 'default' => 'local', + 'default' => env('STORAGE_TYPE', 'local'), + + /* + |-------------------------------------------------------------------------- + | Storage URL + |-------------------------------------------------------------------------- + | + | This is the url to where the storage is located for when using an external + | file storage service, such as s3, to store publicly accessible assets. + | + */ + 'url' => env('STORAGE_URL', false), /* |-------------------------------------------------------------------------- diff --git a/config/services.php b/config/services.php index 604a8657f..08c2f3217 100644 --- a/config/services.php +++ b/config/services.php @@ -13,6 +13,8 @@ return [ | to have a conventional place to find your various credentials. | */ + 'disable_services' => env('DISABLE_EXTERNAL_SERVICES', false), + 'callback_url' => env('APP_URL', false), 'mailgun' => [ 'domain' => '', @@ -47,4 +49,12 @@ return [ 'redirect' => env('APP_URL') . '/login/service/google/callback', ], + 'ldap' => [ + 'server' => env('LDAP_SERVER', false), + 'dn' => env('LDAP_DN', false), + 'pass' => env('LDAP_PASS', false), + 'base_dn' => env('LDAP_BASE_DN', false), + 'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))') + ] + ]; diff --git a/resources/views/auth/forms/login/ldap.blade.php b/resources/views/auth/forms/login/ldap.blade.php new file mode 100644 index 000000000..aa9109345 --- /dev/null +++ b/resources/views/auth/forms/login/ldap.blade.php @@ -0,0 +1,9 @@ +
+ + @include('form/text', ['name' => 'email', 'tabindex' => 1]) +
+ +
+ + @include('form/password', ['name' => 'password', 'tabindex' => 2]) +
\ No newline at end of file diff --git a/resources/views/auth/forms/login/standard.blade.php b/resources/views/auth/forms/login/standard.blade.php new file mode 100644 index 000000000..d9be2935a --- /dev/null +++ b/resources/views/auth/forms/login/standard.blade.php @@ -0,0 +1,10 @@ +
+ + @include('form/text', ['name' => 'email', 'tabindex' => 1]) +
+ +
+ + @include('form/password', ['name' => 'password', 'tabindex' => 2]) + Forgot Password? +
\ No newline at end of file diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index eb9fdb44d..d1239b52c 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -15,16 +15,8 @@
{!! csrf_field() !!} -
- - @include('form/text', ['name' => 'email', 'tabindex' => 1]) -
-
- - @include('form/password', ['name' => 'password', 'tabindex' => 2]) - Forgot Password? -
+ @include('auth/forms/login/' . $authMethod)
@@ -34,7 +26,7 @@
- +
From 1c8c9e65c5379ed4b21b8f893f589df183fb5ff0 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 11 Jan 2016 22:41:05 +0000 Subject: [PATCH 05/11] Got LDAP auth working to a functional state --- app/Http/Controllers/Auth/AuthController.php | 24 +++- app/Http/routes.php | 2 +- app/Providers/AuthServiceProvider.php | 2 +- app/Providers/LdapUserProvider.php | 35 ++++-- app/Repos/UserRepo.php | 3 +- app/Role.php | 2 +- app/Services/LdapService.php | 115 ++++++++++++++---- config/auth.php | 2 +- ...1_11_210908_add_external_auth_to_users.php | 31 +++++ .../views/auth/forms/login/ldap.blade.php | 4 +- 10 files changed, 175 insertions(+), 45 deletions(-) create mode 100644 database/migrations/2016_01_11_210908_add_external_auth_to_users.php diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index c86e0d789..98ef67987 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -2,6 +2,7 @@ namespace BookStack\Http\Controllers\Auth; +use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Http\Request; use BookStack\Exceptions\SocialSignInException; use BookStack\Exceptions\UserRegistrationException; @@ -31,6 +32,8 @@ class AuthController extends Controller protected $redirectPath = '/'; protected $redirectAfterLogout = '/login'; + protected $username = 'email'; + protected $socialAuthService; protected $emailConfirmationService; @@ -48,6 +51,7 @@ class AuthController extends Controller $this->socialAuthService = $socialAuthService; $this->emailConfirmationService = $emailConfirmationService; $this->userRepo = $userRepo; + $this->username = config('auth.method') === 'standard' ? 'email' : 'username'; parent::__construct(); } @@ -104,6 +108,24 @@ class AuthController extends Controller return $this->registerUser($userData); } + + /** + * Overrides the action when a user is authenticated. + * If the user authenticated but does not exist in the user table we create them. + * @param Request $request + * @param Authenticatable $user + * @return \Illuminate\Http\RedirectResponse + */ + protected function authenticated(Request $request, Authenticatable $user) + { + if(!$user->exists) { + $user->save(); + $this->userRepo->attachDefaultRole($user); + auth()->login($user); + } + return redirect()->intended($this->redirectPath()); + } + /** * Register a new user after a registration callback. * @param $socialDriver @@ -232,7 +254,7 @@ class AuthController extends Controller public function getLogin() { $socialDrivers = $this->socialAuthService->getActiveDrivers(); - $authMethod = 'standard'; // TODO - rewrite to use config. + $authMethod = config('auth.method'); return view('auth/login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]); } diff --git a/app/Http/routes.php b/app/Http/routes.php index a8eb6b6e4..aedfca9bc 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -3,7 +3,7 @@ Route::get('/test', function() { // TODO - remove this $service = new \BookStack\Services\LdapService(); - $service->getUserDetails('ssmith'); + dd($service->getUserDetails('ksmith')); }); // Authenticated routes... diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 40e94b016..c027578a7 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -25,7 +25,7 @@ class AuthServiceProvider extends ServiceProvider public function register() { Auth::provider('ldap', function($app, array $config) { - return new LdapUserProvider($config['model']); + return new LdapUserProvider($config['model'], $app['BookStack\Services\LdapService']); }); } } diff --git a/app/Providers/LdapUserProvider.php b/app/Providers/LdapUserProvider.php index c2b961a34..407791a7d 100644 --- a/app/Providers/LdapUserProvider.php +++ b/app/Providers/LdapUserProvider.php @@ -3,6 +3,8 @@ namespace BookStack\Providers; +use BookStack\Role; +use BookStack\Services\LdapService; use BookStack\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\UserProvider; @@ -17,14 +19,21 @@ class LdapUserProvider implements UserProvider */ protected $model; + /** + * @var LdapService + */ + protected $ldapService; + /** * LdapUserProvider constructor. - * @param $model + * @param $model + * @param LdapService $ldapService */ - public function __construct($model) + public function __construct($model, LdapService $ldapService) { $this->model = $model; + $this->ldapService = $ldapService; } /** @@ -34,8 +43,7 @@ class LdapUserProvider implements UserProvider */ public function createModel() { - $class = '\\'.ltrim($this->model, '\\'); - + $class = '\\' . ltrim($this->model, '\\'); return new $class; } @@ -55,7 +63,7 @@ class LdapUserProvider implements UserProvider * Retrieve a user by their unique identifier and "remember me" token. * * @param mixed $identifier - * @param string $token + * @param string $token * @return \Illuminate\Contracts\Auth\Authenticatable|null */ public function retrieveByToken($identifier, $token) @@ -91,16 +99,21 @@ class LdapUserProvider implements UserProvider */ public function retrieveByCredentials(array $credentials) { - // TODO: Implement retrieveByCredentials() method. - // Get user via LDAP + $userDetails = $this->ldapService->getUserDetails($credentials['username']); + if ($userDetails === null) return null; // Search current user base by looking up a uid + $model = $this->createModel(); + $currentUser = $model->newQuery() + ->where('external_auth_id', $userDetails['uid']) + ->first(); - // If not exists create a new user instance with attached role - // but do not store it in the database yet + if ($currentUser !== null) return $currentUser; - // + $model->name = $userDetails['name']; + $model->external_auth_id = $userDetails['uid']; + return $model; } /** @@ -112,6 +125,6 @@ class LdapUserProvider implements UserProvider */ public function validateCredentials(Authenticatable $user, array $credentials) { - // TODO: Implement validateCredentials() method. + return $this->ldapService->validateUserCredentials($user, $credentials['username'], $credentials['password']); } } diff --git a/app/Repos/UserRepo.php b/app/Repos/UserRepo.php index fecd7c88b..88918910a 100644 --- a/app/Repos/UserRepo.php +++ b/app/Repos/UserRepo.php @@ -3,6 +3,7 @@ use BookStack\Role; use BookStack\User; +use Setting; class UserRepo { @@ -56,7 +57,7 @@ class UserRepo */ public function attachDefaultRole($user) { - $roleId = \Setting::get('registration-role'); + $roleId = Setting::get('registration-role'); if ($roleId === false) $roleId = $this->role->getDefault()->id; $user->attachRoleId($roleId); } diff --git a/app/Role.php b/app/Role.php index c698a1cf6..3d93bf770 100644 --- a/app/Role.php +++ b/app/Role.php @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Model; class Role extends Model { /** - * Sets the default role name for newly registed users. + * Sets the default role name for newly registered users. * @var string */ protected static $default = 'viewer'; diff --git a/app/Services/LdapService.php b/app/Services/LdapService.php index a540ab58b..bceed682a 100644 --- a/app/Services/LdapService.php +++ b/app/Services/LdapService.php @@ -2,18 +2,94 @@ use BookStack\Exceptions\LdapException; +use Illuminate\Contracts\Auth\Authenticatable; class LdapService { + protected $ldapConnection; + + /** + * Get the details of a user from LDAP using the given username. + * User found via configurable user filter. + * @param $userName + * @return array|null + * @throws LdapException + */ public function getUserDetails($userName) { + $ldapConnection = $this->getConnection(); - if(!function_exists('ldap_connect')) { + // Find user + $userFilter = $this->buildFilter(config('services.ldap.user_filter'), ['user' => $userName]); + $baseDn = config('services.ldap.base_dn'); + $ldapSearch = ldap_search($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn']); + $users = ldap_get_entries($ldapConnection, $ldapSearch); + if ($users['count'] === 0) return null; + + $user = $users[0]; + return [ + 'uid' => $user['uid'][0], + 'name' => $user['cn'][0], + 'dn' => $user['dn'] + ]; + } + + /** + * @param Authenticatable $user + * @param string $username + * @param string $password + * @return bool + * @throws LdapException + */ + public function validateUserCredentials(Authenticatable $user, $username, $password) + { + $ldapUser = $this->getUserDetails($username); + if ($ldapUser === null) return false; + if ($ldapUser['uid'] !== $user->external_auth_id) return false; + + $ldapConnection = $this->getConnection(); + $ldapBind = @ldap_bind($ldapConnection, $ldapUser['dn'], $password); + return $ldapBind; + } + + /** + * Bind the system user to the LDAP connection using the given credentials + * otherwise anonymous access is attempted. + * @param $connection + * @throws LdapException + */ + protected function bindSystemUser($connection) + { + $ldapDn = config('services.ldap.dn'); + $ldapPass = config('services.ldap.pass'); + + $isAnonymous = ($ldapDn === false || $ldapPass === false); + if ($isAnonymous) { + $ldapBind = ldap_bind($connection); + } else { + $ldapBind = ldap_bind($connection, $ldapDn, $ldapPass); + } + + if (!$ldapBind) throw new LdapException('LDAP access failed using ' . $isAnonymous ? ' anonymous bind.' : ' given dn & pass details'); + } + + /** + * Get the connection to the LDAP server. + * Creates a new connection if one does not exist. + * @return resource + * @throws LdapException + */ + protected function getConnection() + { + if ($this->ldapConnection !== null) return $this->ldapConnection; + + // Check LDAP extension in installed + if (!function_exists('ldap_connect')) { throw new LdapException('LDAP PHP extension not installed'); } - + // Get port from server string if specified. $ldapServer = explode(':', config('services.ldap.server')); $ldapConnection = ldap_connect($ldapServer[0], count($ldapServer) > 1 ? $ldapServer[1] : 389); @@ -21,37 +97,24 @@ class LdapService throw new LdapException('Cannot connect to ldap server, Initial connection failed'); } - // Options - + // Set any required options ldap_set_option($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3); // TODO - make configurable - $ldapDn = config('services.ldap.dn'); - $ldapPass = config('services.ldap.pass'); - $isAnonymous = ($ldapDn === false || $ldapPass === false); - if ($isAnonymous) { - $ldapBind = ldap_bind($ldapConnection); - } else { - $ldapBind = ldap_bind($ldapConnection, $ldapDn, $ldapPass); - } - - if (!$ldapBind) throw new LdapException('LDAP access failed using ' . $isAnonymous ? ' anonymous bind.' : ' given dn & pass details'); - - // Find user - $userFilter = $this->buildFilter(config('services.ldap.user_filter'), ['user' => $userName]); - //dd($userFilter); - $baseDn = config('services.ldap.base_dn'); - $ldapSearch = ldap_search($ldapConnection, $baseDn, $userFilter); - $users = ldap_get_entries($ldapConnection, $ldapSearch); - - dd($users); + $this->ldapConnection = $ldapConnection; + return $this->ldapConnection; } - - private function buildFilter($filterString, $attrs) + /** + * Build a filter string by injecting common variables. + * @param $filterString + * @param array $attrs + * @return string + */ + protected function buildFilter($filterString, array $attrs) { $newAttrs = []; foreach ($attrs as $key => $attrText) { - $newKey = '${'.$key.'}'; + $newKey = '${' . $key . '}'; $newAttrs[$newKey] = $attrText; } return strtr($filterString, $newAttrs); diff --git a/config/auth.php b/config/auth.php index 55d434cdf..0f2d5a69c 100644 --- a/config/auth.php +++ b/config/auth.php @@ -70,7 +70,7 @@ return [ 'providers' => [ 'users' => [ 'driver' => env('AUTH_METHOD', 'eloquent'), - 'model' => Bookstack\User::class, + 'model' => BookStack\User::class, ], // 'users' => [ diff --git a/database/migrations/2016_01_11_210908_add_external_auth_to_users.php b/database/migrations/2016_01_11_210908_add_external_auth_to_users.php new file mode 100644 index 000000000..dda8f3d74 --- /dev/null +++ b/database/migrations/2016_01_11_210908_add_external_auth_to_users.php @@ -0,0 +1,31 @@ +string('external_auth_id')->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('external_auth_id'); + }); + } +} diff --git a/resources/views/auth/forms/login/ldap.blade.php b/resources/views/auth/forms/login/ldap.blade.php index aa9109345..eb0a3182f 100644 --- a/resources/views/auth/forms/login/ldap.blade.php +++ b/resources/views/auth/forms/login/ldap.blade.php @@ -1,6 +1,6 @@
- - @include('form/text', ['name' => 'email', 'tabindex' => 1]) + + @include('form/text', ['name' => 'username', 'tabindex' => 1])
From 14feef3679b6ecdce656d56dd754357997084632 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 13 Jan 2016 22:22:30 +0000 Subject: [PATCH 06/11] Updated user interfaces for LDAP and added email from LDAP --- app/Http/Controllers/Auth/AuthController.php | 15 ++++++++-- app/Http/Controllers/UserController.php | 16 ++++++++-- app/Providers/LdapUserProvider.php | 2 +- app/Repos/UserRepo.php | 2 +- app/Services/LdapService.php | 5 ++-- app/Services/SettingService.php | 28 +++++++++++++++-- app/User.php | 4 +-- config/auth.php | 2 +- phpunit.xml | 1 + .../views/auth/forms/login/ldap.blade.php | 10 +++++++ resources/views/users/create.blade.php | 2 +- resources/views/users/edit.blade.php | 2 +- resources/views/users/forms/ldap.blade.php | 30 +++++++++++++++++++ .../standard.blade.php} | 8 ++--- 14 files changed, 106 insertions(+), 21 deletions(-) create mode 100644 resources/views/users/forms/ldap.blade.php rename resources/views/users/{form.blade.php => forms/standard.blade.php} (81%) diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index 98ef67987..21abfb24c 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -118,11 +118,22 @@ class AuthController extends Controller */ protected function authenticated(Request $request, Authenticatable $user) { + if(!$user->exists && $user->email === null && !$request->has('email')) { + $request->flash(); + session()->flash('request-email', true); + return redirect('/login'); + } + + if(!$user->exists && $user->email === null && $request->has('email')) { + $user->email = $request->get('email'); + } + if(!$user->exists) { $user->save(); $this->userRepo->attachDefaultRole($user); auth()->login($user); } + return redirect()->intended($this->redirectPath()); } @@ -183,7 +194,7 @@ class AuthController extends Controller } /** - * Show the page to tell the user to check thier email + * Show the page to tell the user to check their email * and confirm their address. */ public function getRegisterConfirmation() @@ -243,7 +254,7 @@ class AuthController extends Controller ]); $user = $this->userRepo->getByEmail($request->get('email')); $this->emailConfirmationService->sendConfirmation($user); - \Session::flash('success', 'Confirmation email resent, Please check your inbox.'); + session()->flash('success', 'Confirmation email resent, Please check your inbox.'); return redirect('/register/confirm'); } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 9184b245e..f504f4477 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -46,7 +46,8 @@ class UserController extends Controller public function create() { $this->checkPermission('user-create'); - return view('users/create'); + $authMethod = config('auth.method'); + return view('users/create', ['authMethod' => $authMethod]); } /** @@ -94,10 +95,12 @@ class UserController extends Controller return $this->currentUser->id == $id; }); + $authMethod = config('auth.method'); + $user = $this->user->findOrFail($id); $activeSocialDrivers = $socialAuthService->getActiveDrivers(); $this->setPageTitle('User Profile'); - return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers]); + return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod]); } /** @@ -124,17 +127,24 @@ class UserController extends Controller ]); $user = $this->user->findOrFail($id); - $user->fill($request->except('password')); + $user->fill($request->all()); + // Role updates if ($this->currentUser->can('user-update') && $request->has('role')) { $user->attachRoleId($request->get('role')); } + // Password updates if ($request->has('password') && $request->get('password') != '') { $password = $request->get('password'); $user->password = bcrypt($password); } + // External auth id updates + if ($this->currentUser->can('user-update') && $request->has('external_auth_id')) { + $user->external_auth_id = $request->get('external_auth_id'); + } + $user->save(); return redirect('/users'); } diff --git a/app/Providers/LdapUserProvider.php b/app/Providers/LdapUserProvider.php index 407791a7d..98cfc8340 100644 --- a/app/Providers/LdapUserProvider.php +++ b/app/Providers/LdapUserProvider.php @@ -87,7 +87,6 @@ class LdapUserProvider implements UserProvider public function updateRememberToken(Authenticatable $user, $token) { $user->setRememberToken($token); - $user->save(); } @@ -113,6 +112,7 @@ class LdapUserProvider implements UserProvider $model->name = $userDetails['name']; $model->external_auth_id = $userDetails['uid']; + $model->email = $userDetails['email']; return $model; } diff --git a/app/Repos/UserRepo.php b/app/Repos/UserRepo.php index 88918910a..77ad22f39 100644 --- a/app/Repos/UserRepo.php +++ b/app/Repos/UserRepo.php @@ -88,7 +88,7 @@ class UserRepo */ public function create(array $data) { - return $this->user->create([ + return $this->user->forceCreate([ 'name' => $data['name'], 'email' => $data['email'], 'password' => bcrypt($data['password']) diff --git a/app/Services/LdapService.php b/app/Services/LdapService.php index bceed682a..cd80290e4 100644 --- a/app/Services/LdapService.php +++ b/app/Services/LdapService.php @@ -23,7 +23,7 @@ class LdapService // Find user $userFilter = $this->buildFilter(config('services.ldap.user_filter'), ['user' => $userName]); $baseDn = config('services.ldap.base_dn'); - $ldapSearch = ldap_search($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn']); + $ldapSearch = ldap_search($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', 'mail']); $users = ldap_get_entries($ldapConnection, $ldapSearch); if ($users['count'] === 0) return null; @@ -31,7 +31,8 @@ class LdapService return [ 'uid' => $user['uid'][0], 'name' => $user['cn'][0], - 'dn' => $user['dn'] + 'dn' => $user['dn'], + 'email' => (isset($user['mail'])) ? $user['mail'][0] : null ]; } diff --git a/app/Services/SettingService.php b/app/Services/SettingService.php index 7f2549c7d..bcc7eae31 100644 --- a/app/Services/SettingService.php +++ b/app/Services/SettingService.php @@ -38,7 +38,7 @@ class SettingService */ public function get($key, $default = false) { - $value = $this->getValueFromStore($key, $default); + $value = $this->getValueFromStore($key, $default); return $this->formatValue($value, $default); } @@ -50,13 +50,17 @@ class SettingService */ protected function getValueFromStore($key, $default) { + $overrideValue = $this->getOverrideValue($key); + if ($overrideValue !== null) return $overrideValue; + $cacheKey = $this->cachePrefix . $key; if ($this->cache->has($cacheKey)) { return $this->cache->get($cacheKey); } $settingObject = $this->getSettingObjectByKey($key); - if($settingObject !== null) { + + if ($settingObject !== null) { $value = $settingObject->value; $this->cache->forever($cacheKey, $value); return $value; @@ -65,6 +69,10 @@ class SettingService return $default; } + /** + * Clear an item from the cache completely. + * @param $key + */ protected function clearFromCache($key) { $cacheKey = $this->cachePrefix . $key; @@ -136,9 +144,23 @@ class SettingService * @param $key * @return mixed */ - private function getSettingObjectByKey($key) + protected function getSettingObjectByKey($key) { return $this->setting->where('setting_key', '=', $key)->first(); } + + /** + * Returns an override value for a setting based on certain app conditions. + * Used where certain configuration options overrule others. + * Returns null if no override value is available. + * @param $key + * @return bool|null + */ + protected function getOverrideValue($key) + { + if ($key === 'registration-enabled' && config('auth.method') === 'ldap') return false; + return null; + } + } \ No newline at end of file diff --git a/app/User.php b/app/User.php index 1be98c3c4..4a5914afd 100644 --- a/app/User.php +++ b/app/User.php @@ -24,7 +24,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon * * @var array */ - protected $fillable = ['name', 'email', 'password', 'image_id']; + protected $fillable = ['name', 'email', 'image_id']; /** * The attributes excluded from the model's JSON form. @@ -68,7 +68,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon } /** - * Loads the user's permissions from thier role. + * Loads the user's permissions from their role. */ private function loadPermissions() { diff --git a/config/auth.php b/config/auth.php index 0f2d5a69c..ceeab5c71 100644 --- a/config/auth.php +++ b/config/auth.php @@ -69,7 +69,7 @@ return [ 'providers' => [ 'users' => [ - 'driver' => env('AUTH_METHOD', 'eloquent'), + 'driver' => env('AUTH_METHOD', 'standard') === 'standard' ? 'eloquent' : env('AUTH_METHOD'), 'model' => BookStack\User::class, ], diff --git a/phpunit.xml b/phpunit.xml index 1704159e2..48c0dde22 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -26,6 +26,7 @@ + diff --git a/resources/views/auth/forms/login/ldap.blade.php b/resources/views/auth/forms/login/ldap.blade.php index eb0a3182f..5230d43ca 100644 --- a/resources/views/auth/forms/login/ldap.blade.php +++ b/resources/views/auth/forms/login/ldap.blade.php @@ -3,6 +3,16 @@ @include('form/text', ['name' => 'username', 'tabindex' => 1])
+@if(session('request-email', false) === true) +
+ + @include('form/text', ['name' => 'email', 'tabindex' => 1]) + + Please enter an email to use for this account. + +
+@endif +
@include('form/password', ['name' => 'password', 'tabindex' => 2]) diff --git a/resources/views/users/create.blade.php b/resources/views/users/create.blade.php index f20ae6fc4..e6398b867 100644 --- a/resources/views/users/create.blade.php +++ b/resources/views/users/create.blade.php @@ -8,7 +8,7 @@
{!! csrf_field() !!} - @include('users/form') + @include('users.forms.' . $authMethod)
diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index e7100bdb1..59457eb15 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -25,7 +25,7 @@

Edit {{ $user->id === $currentUser->id ? 'Profile' : 'User' }}

{!! csrf_field() !!} - @include('users/form', ['model' => $user]) + @include('users.forms.' . $authMethod, ['model' => $user])
diff --git a/resources/views/users/forms/ldap.blade.php b/resources/views/users/forms/ldap.blade.php new file mode 100644 index 000000000..3897dfd9a --- /dev/null +++ b/resources/views/users/forms/ldap.blade.php @@ -0,0 +1,30 @@ +
+ + @include('form.text', ['name' => 'name']) +
+ +@if($currentUser->can('user-update')) +
+ + @include('form.text', ['name' => 'email']) +
+@endif + +@if($currentUser->can('user-update')) +
+ + @include('form.role-select', ['name' => 'role', 'options' => \BookStack\Role::all(), 'displayKey' => 'display_name']) +
+@endif + +@if($currentUser->can('user-update')) +
+ + @include('form.text', ['name' => 'external_auth_id']) +
+@endif + +
+ Cancel + +
\ No newline at end of file diff --git a/resources/views/users/form.blade.php b/resources/views/users/forms/standard.blade.php similarity index 81% rename from resources/views/users/form.blade.php rename to resources/views/users/forms/standard.blade.php index 16176bb8d..7960a7ed5 100644 --- a/resources/views/users/form.blade.php +++ b/resources/views/users/forms/standard.blade.php @@ -1,11 +1,11 @@
- @include('form/text', ['name' => 'name']) + @include('form.text', ['name' => 'name'])
- @include('form/text', ['name' => 'email']) + @include('form.text', ['name' => 'email'])
@if($currentUser->can('user-update')) @@ -25,12 +25,12 @@
- @include('form/password', ['name' => 'password']) + @include('form.password', ['name' => 'password'])
- @include('form/password', ['name' => 'password-confirm']) + @include('form.password', ['name' => 'password-confirm'])
From 0821672e70cf9eb81091032514634b299cc5900b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 15 Jan 2016 23:21:47 +0000 Subject: [PATCH 07/11] Cleaned tests up, Started LDAP tests, Created LDAP wrapper --- .env.example | 8 ++ app/Http/Controllers/Auth/AuthController.php | 9 +- app/Http/Middleware/Authenticate.php | 1 + app/Http/routes.php | 6 -- app/Providers/LdapUserProvider.php | 7 +- app/Services/Ldap.php | 86 ++++++++++++++++++++ app/Services/LdapService.php | 57 +++++++++---- config/services.php | 3 +- resources/views/public.blade.php | 17 ++-- tests/ActivityTrackingTest.php | 4 +- tests/{ => Auth}/AuthTest.php | 58 +++++++++---- tests/Auth/LdapTest.php | 43 ++++++++++ tests/{ => Auth}/SocialAuthTest.php | 11 +-- tests/EntityTest.php | 20 ++--- tests/PublicViewTest.php | 8 +- 15 files changed, 259 insertions(+), 79 deletions(-) create mode 100644 app/Services/Ldap.php rename tests/{ => Auth}/AuthTest.php (78%) create mode 100644 tests/Auth/LdapTest.php rename tests/{ => Auth}/SocialAuthTest.php (82%) diff --git a/.env.example b/.env.example index 00d230bff..d32d96c0d 100644 --- a/.env.example +++ b/.env.example @@ -36,6 +36,14 @@ APP_URL=http://bookstack.dev # External services such as Gravatar DISABLE_EXTERNAL_SERVICES=false +# LDAP Settings +LDAP_SERVER=false +LDAP_BASE_DN=false +LDAP_DN=false +LDAP_PASS=false +LDAP_USER_FILTER=false +LDAP_VERSION=false + # Mail settings MAIL_DRIVER=smtp MAIL_HOST=localhost diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index 21abfb24c..d601b4985 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -118,17 +118,20 @@ class AuthController extends Controller */ protected function authenticated(Request $request, Authenticatable $user) { - if(!$user->exists && $user->email === null && !$request->has('email')) { + // Explicitly log them out for now if they do no exist. + if (!$user->exists) auth()->logout($user); + + if (!$user->exists && $user->email === null && !$request->has('email')) { $request->flash(); session()->flash('request-email', true); return redirect('/login'); } - if(!$user->exists && $user->email === null && $request->has('email')) { + if (!$user->exists && $user->email === null && $request->has('email')) { $user->email = $request->get('email'); } - if(!$user->exists) { + if (!$user->exists) { $user->save(); $this->userRepo->attachDefaultRole($user); auth()->login($user); diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index ebd830ffe..ad804d0d8 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -38,6 +38,7 @@ class Authenticate if(auth()->check() && auth()->user()->email_confirmed == false) { return redirect()->guest('/register/confirm/awaiting'); } + if ($this->auth->guest() && !Setting::get('app-public')) { if ($request->ajax()) { return response('Unauthorized.', 401); diff --git a/app/Http/routes.php b/app/Http/routes.php index aedfca9bc..23d4c33ab 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -1,11 +1,5 @@ getUserDetails('ksmith')); -}); - // Authenticated routes... Route::group(['middleware' => 'auth'], function () { diff --git a/app/Providers/LdapUserProvider.php b/app/Providers/LdapUserProvider.php index 98cfc8340..30fa739c2 100644 --- a/app/Providers/LdapUserProvider.php +++ b/app/Providers/LdapUserProvider.php @@ -86,8 +86,10 @@ class LdapUserProvider implements UserProvider */ public function updateRememberToken(Authenticatable $user, $token) { - $user->setRememberToken($token); - $user->save(); + if ($user->exists) { + $user->setRememberToken($token); + $user->save(); + } } /** @@ -113,6 +115,7 @@ class LdapUserProvider implements UserProvider $model->name = $userDetails['name']; $model->external_auth_id = $userDetails['uid']; $model->email = $userDetails['email']; + $model->email_confirmed = true; return $model; } diff --git a/app/Services/Ldap.php b/app/Services/Ldap.php new file mode 100644 index 000000000..cfefbb4b6 --- /dev/null +++ b/app/Services/Ldap.php @@ -0,0 +1,86 @@ +search($ldapConnection, $baseDn, $filter, $attributes); + return $this->getEntries($ldapConnection, $search); + } + + /** + * Bind to LDAP directory. + * @param resource $ldapConnection + * @param string $bindRdn + * @param string $bindPassword + * @return bool + */ + public function bind($ldapConnection, $bindRdn = null, $bindPassword = null) + { + return ldap_bind($ldapConnection, $bindRdn, $bindPassword); + } + +} \ No newline at end of file diff --git a/app/Services/LdapService.php b/app/Services/LdapService.php index cd80290e4..d33f8c378 100644 --- a/app/Services/LdapService.php +++ b/app/Services/LdapService.php @@ -4,10 +4,27 @@ use BookStack\Exceptions\LdapException; use Illuminate\Contracts\Auth\Authenticatable; +/** + * Class LdapService + * Handles any app-specific LDAP tasks. + * @package BookStack\Services + */ class LdapService { + protected $ldap; protected $ldapConnection; + protected $config; + + /** + * LdapService constructor. + * @param Ldap $ldap + */ + public function __construct(Ldap $ldap) + { + $this->ldap = $ldap; + $this->config = config('services.ldap'); + } /** * Get the details of a user from LDAP using the given username. @@ -21,17 +38,16 @@ class LdapService $ldapConnection = $this->getConnection(); // Find user - $userFilter = $this->buildFilter(config('services.ldap.user_filter'), ['user' => $userName]); - $baseDn = config('services.ldap.base_dn'); - $ldapSearch = ldap_search($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', 'mail']); - $users = ldap_get_entries($ldapConnection, $ldapSearch); + $userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]); + $baseDn = $this->config['base_dn']; + $users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', 'mail']); if ($users['count'] === 0) return null; $user = $users[0]; return [ - 'uid' => $user['uid'][0], - 'name' => $user['cn'][0], - 'dn' => $user['dn'], + 'uid' => $user['uid'][0], + 'name' => $user['cn'][0], + 'dn' => $user['dn'], 'email' => (isset($user['mail'])) ? $user['mail'][0] : null ]; } @@ -50,7 +66,12 @@ class LdapService if ($ldapUser['uid'] !== $user->external_auth_id) return false; $ldapConnection = $this->getConnection(); - $ldapBind = @ldap_bind($ldapConnection, $ldapUser['dn'], $password); + try { + $ldapBind = $this->ldap->bind($ldapConnection, $ldapUser['dn'], $password); + } catch (\ErrorException $e) { + $ldapBind = false; + } + return $ldapBind; } @@ -62,14 +83,14 @@ class LdapService */ protected function bindSystemUser($connection) { - $ldapDn = config('services.ldap.dn'); - $ldapPass = config('services.ldap.pass'); + $ldapDn = $this->config['dn']; + $ldapPass = $this->config['pass']; $isAnonymous = ($ldapDn === false || $ldapPass === false); if ($isAnonymous) { - $ldapBind = ldap_bind($connection); + $ldapBind = $this->ldap->bind($connection); } else { - $ldapBind = ldap_bind($connection, $ldapDn, $ldapPass); + $ldapBind = $this->ldap->bind($connection, $ldapDn, $ldapPass); } if (!$ldapBind) throw new LdapException('LDAP access failed using ' . $isAnonymous ? ' anonymous bind.' : ' given dn & pass details'); @@ -86,20 +107,22 @@ class LdapService if ($this->ldapConnection !== null) return $this->ldapConnection; // Check LDAP extension in installed - if (!function_exists('ldap_connect')) { + if (!function_exists('ldap_connect') && config('app.env') !== 'testing') { throw new LdapException('LDAP PHP extension not installed'); } // Get port from server string if specified. - $ldapServer = explode(':', config('services.ldap.server')); - $ldapConnection = ldap_connect($ldapServer[0], count($ldapServer) > 1 ? $ldapServer[1] : 389); + $ldapServer = explode(':', $this->config['server']); + $ldapConnection = $this->ldap->connect($ldapServer[0], count($ldapServer) > 1 ? $ldapServer[1] : 389); if ($ldapConnection === false) { throw new LdapException('Cannot connect to ldap server, Initial connection failed'); } // Set any required options - ldap_set_option($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3); // TODO - make configurable + if ($this->config['version']) { + $this->ldap->setOption($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, $this->config['version']); + } $this->ldapConnection = $ldapConnection; return $this->ldapConnection; @@ -107,7 +130,7 @@ class LdapService /** * Build a filter string by injecting common variables. - * @param $filterString + * @param string $filterString * @param array $attrs * @return string */ diff --git a/config/services.php b/config/services.php index 08c2f3217..d71ff4a26 100644 --- a/config/services.php +++ b/config/services.php @@ -54,7 +54,8 @@ return [ 'dn' => env('LDAP_DN', false), 'pass' => env('LDAP_PASS', false), 'base_dn' => env('LDAP_BASE_DN', false), - 'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))') + 'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'), + 'version' => env('LDAP_VERSION', false) ] ]; diff --git a/resources/views/public.blade.php b/resources/views/public.blade.php index 52c287987..d34c490a7 100644 --- a/resources/views/public.blade.php +++ b/resources/views/public.blade.php @@ -5,19 +5,19 @@ + - - + - + @include('partials/notifications') @@ -37,12 +37,15 @@ @yield('header-buttons')
@if(isset($signedIn) && $signedIn) - {{ $currentUser->name }} -