diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 8a76e49fe9..6fa361b448 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -8,6 +8,7 @@ use App\Http\Transformers\UsersTransformer; use App\Models\Company; use App\Models\User; use App\Helpers\Helper; +use App\Http\Requests\SaveUserRequest; class UsersController extends Controller { @@ -102,7 +103,7 @@ class UsersController extends Controller * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ - public function store(Request $request) + public function store(SaveUserRequest $request) { $this->authorize('view', User::class); $user = new User; @@ -139,7 +140,7 @@ class UsersController extends Controller * @param int $id * @return \Illuminate\Http\Response */ - public function update(Request $request, $id) + public function update(SaveUserRequest $request, $id) { $this->authorize('edit', User::class); $user = User::findOrFail($id); diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index 46697818ee..1d66b55fca 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -414,7 +414,7 @@ class AssetModelsController extends Controller $manufacturer_list = $nochange + Helper::manufacturerList(); - return view('models/bulk-edit', compact('models')) + return view('models/bulk-edit', compact('models')) ->with('manufacturer_list', $manufacturer_list) ->with('category_list', $category_list) ->with('fieldset_list', $fieldset_list) diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 6dd7cc04c6..de12e48441 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -462,6 +462,15 @@ class SettingsController extends Controller } + $setting->pwd_secure_uncommon = (int) $request->input('pwd_secure_uncommon'); + $setting->pwd_secure_min = (int) $request->input('pwd_secure_min'); + $setting->pwd_secure_complexity = ''; + + if ($request->has('pwd_secure_complexity')) { + $setting->pwd_secure_complexity = implode('|', $request->input('pwd_secure_complexity')); + } + + if ($setting->save()) { return redirect()->route('settings.index') diff --git a/app/Http/Controllers/UsersController.php b/app/Http/Controllers/UsersController.php index 0045b514d1..2b0c12bc98 100755 --- a/app/Http/Controllers/UsersController.php +++ b/app/Http/Controllers/UsersController.php @@ -12,9 +12,7 @@ use App\Models\Company; use App\Models\Location; use App\Models\License; use App\Models\Setting; -use App\Models\Statuslabel; use App\Http\Requests\SaveUserRequest; -use App\Http\Requests\UpdateUserRequest; use Symfony\Component\HttpFoundation\StreamedResponse; use App\Models\User; use App\Models\Ldap; @@ -23,7 +21,6 @@ use Config; use Crypt; use DB; use HTML; -use Illuminate\Support\Facades\Log; use Input; use Lang; use League\Csv\Reader; @@ -169,7 +166,7 @@ class UsersController extends Controller * @since [v1.8] * @return string JSON */ - public function apiStore(Request $request) + public function apiStore(SaveUserRequest $request) { $this->authorize('create', User::class); @@ -270,7 +267,7 @@ class UsersController extends Controller * @param int $id * @return \Illuminate\Http\RedirectResponse */ - public function update(UpdateUserRequest $request, $id = null) + public function update(SaveUserRequest $request, $id = null) { // We need to reverse the UI specific logic for our // permissions here before we update the user. @@ -309,14 +306,11 @@ class UsersController extends Controller } } - // Do we want to update the user password? - if ($request->has('password')) { - $user->password = bcrypt($request->input('password')); - } + if ($request->has('username')) { - $user->username = e($request->input('username')); + $user->username = $request->input('username'); } - $user->email = e($request->input('email')); + $user->email = $request->input('email'); // Update the user @@ -334,6 +328,12 @@ class UsersController extends Controller $user->notes = $request->input('notes'); $user->department_id = $request->input('department_id', null); + + // Do we want to update the user password? + if ($request->has('password')) { + $user->password = bcrypt($request->input('password')); + } + // Strip out the superuser permission if the user isn't a superadmin $permissions_array = $request->input('permission'); diff --git a/app/Http/Requests/SaveUserRequest.php b/app/Http/Requests/SaveUserRequest.php index 76a36985b9..211973a8f5 100644 --- a/app/Http/Requests/SaveUserRequest.php +++ b/app/Http/Requests/SaveUserRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests; use App\Http\Requests\Request; +use App\Models\Setting; class SaveUserRequest extends Request { @@ -23,12 +24,35 @@ class SaveUserRequest extends Request */ public function rules() { - return [ - 'first_name' => 'required|string|min:1', - 'email' => 'email', - 'password' => 'required|min:6', - 'password_confirm' => 'sometimes|required_with:password', - 'username' => 'required|string|min:2|unique:users,username,NULL,deleted_at', - ]; + + $settings = Setting::getSettings(); + + $rules = []; + $security_rules = ''; + + $rules['first_name'] = 'required|string|min:1'; + $rules['username'] = 'required|string|min:1|unique_undeleted'; + + // Check if they have uncommon password enforcement selected in settings + if ($settings->pwd_secure_uncommon == 1) { + $security_rules .= '|dumbpwd'; + } + + // Check for any secure password complexity rules that may have been selected + if ($settings->pwd_secure_complexity!='') { + $security_rules .= '|'.$settings->pwd_secure_complexity; + } + + + if ((\Route::currentRouteName()=='api.users.update') || (\Route::currentRouteName()=='users.update')) { + $rules['password'] = 'nullable|min:'.$settings->pwd_secure_min.$security_rules; + } else { + $rules['password'] = 'required|min:'.$settings->pwd_secure_min.$security_rules; + } + + $rules['password_confirm'] = 'sometimes|required_with:password'; + + return $rules; + } } diff --git a/app/Http/Requests/UpdateUserRequest.php b/app/Http/Requests/UpdateUserRequest.php deleted file mode 100644 index b778de8e6c..0000000000 --- a/app/Http/Requests/UpdateUserRequest.php +++ /dev/null @@ -1,32 +0,0 @@ - 'required|string|min:1', - 'email' => 'email', - 'password_confirm' => 'sometimes|required_with:password', - ]; - } -} diff --git a/app/Models/Setting.php b/app/Models/Setting.php index c2ba250837..cf51f2460c 100755 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -44,6 +44,7 @@ class Setting extends Model "ldap_auth_filter_query" => 'sometimes|required_if:ldap_enabled,1|nullable', "ldap_version" => 'sometimes|required_if:ldap_enabled,1|nullable', "thumbnail_max_h" => 'numeric|max:500|min:25', + "pwd_secure_min" => "numeric|required|min:5", ]; protected $fillable = ['site_name','email_domain','email_format','username_format']; @@ -158,4 +159,34 @@ class Setting extends Model // In the future this may want to be adapted for individual notifications. return $this->slack_endpoint; } + + public function passwordComplexityStringToArray() + { + + $this->pwd_secure_complexity = 'numbers|letters|case_diff'; + $complexity_array_split = array(); + $complexity_array = array(); + + if (($this->pwd_secure_complexity) && ($this->pwd_secure_complexity!='')) { + $complexity_array_split = explode('|',$this->pwd_secure_complexity); + } + + for ($x = 0; $x < count($complexity_array_split); $x++) { + $complexity_array[$complexity_array_split[$x]] = 1; + } + + return $complexity_array; + + } + + public static function passwordComplexityToFormattedString($array) { + // $array = array(); + $string = ''; + for ($x = 0; $x <= count($array); $x++) { + $string .= '|'.$array[$x]; + } + + return $string; + } + } diff --git a/app/Models/User.php b/app/Models/User.php index bc142a3c45..98e0a31df7 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -51,9 +51,9 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo protected $rules = [ 'first_name' => 'required|string|min:1', 'username' => 'required|string|min:1|unique_undeleted', - 'email' => 'email', + 'email' => 'email|nullable', 'password' => 'required|min:6', - 'locale' => 'max:10' + 'locale' => 'max:10|nullable' ]; diff --git a/composer.json b/composer.json index 2f98b961d1..f48514cb59 100644 --- a/composer.json +++ b/composer.json @@ -24,8 +24,10 @@ "neitanod/forceutf8": "^2.0", "patchwork/utf8": "~1.2", "pragmarx/google2fa": "^1.0", + "schuppo/password-strength": "~1.5", "spatie/laravel-backup": "^3.0.0", "tecnickcom/tc-lib-barcode": "^1.15", + "unicodeveloper/laravel-password": "^1.0", "watson/validating": "^3.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 9eb6dbeead..2457449dd5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "dd84e08e5e7eca2c1e032c6357ca2500", + "content-hash": "da7d8408e0c3434a6131340252d2d228", "packages": [ { "name": "aws/aws-sdk-php", @@ -2888,6 +2888,56 @@ ], "time": "2017-08-04T13:39:04+00:00" }, + { + "name": "schuppo/password-strength", + "version": "v1.9", + "source": { + "type": "git", + "url": "https://github.com/schuppo/PasswordStrengthPackage.git", + "reference": "184d65517eb438651b491b116df8895238005b79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schuppo/PasswordStrengthPackage/zipball/184d65517eb438651b491b116df8895238005b79", + "reference": "184d65517eb438651b491b116df8895238005b79", + "shasum": "" + }, + "require": { + "illuminate/support": "~5.0", + "illuminate/translation": "^5.1", + "php": ">=5.4.0" + }, + "require-dev": { + "illuminate/validation": "~5.0", + "phpunit/phpunit": "^4.8" + }, + "type": "library", + "autoload": { + "psr-0": { + "Schuppo\\PasswordStrength": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Schupp", + "email": "oliver.schupp@yahoo.de" + } + ], + "description": "This package provides a validator for ensuring strong passwords in Laravel 4 applications.", + "keywords": [ + "laravel", + "laravel 5", + "laravel5", + "password", + "password strength", + "validation" + ], + "time": "2016-10-05T09:57:59+00:00" + }, { "name": "spatie/db-dumper", "version": "1.5.1", @@ -4171,6 +4221,61 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "time": "2016-09-20T12:50:39+00:00" }, + { + "name": "unicodeveloper/laravel-password", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/unicodeveloper/laravel-password.git", + "reference": "5c3bdc977c4b8065350caf2e57371b069ef6f5b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/unicodeveloper/laravel-password/zipball/5c3bdc977c4b8065350caf2e57371b069ef6f5b4", + "reference": "5c3bdc977c4b8065350caf2e57371b069ef6f5b4", + "shasum": "" + }, + "require": { + "php": "~5.6|~7.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0||~5.0", + "scrutinizer/ocular": "~1.1", + "squizlabs/php_codesniffer": "~2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Unicodeveloper\\DumbPassword\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": ":Prosper Otemuyiwa", + "email": "prosperotemuyiwa@gmail.com", + "homepage": "http://goodheads.io", + "role": "Developer" + } + ], + "description": "Protect your users from entering dumb and common passwords", + "homepage": "https://github.com/unicodeveloper/laravel-password", + "keywords": [ + "dumb", + "passwords", + "security", + "unicodeveloper" + ], + "time": "2017-04-27T07:35:00+00:00" + }, { "name": "vlucas/phpdotenv", "version": "v2.4.0", diff --git a/config/app.php b/config/app.php index f210cd44f9..5fbfc80739 100755 --- a/config/app.php +++ b/config/app.php @@ -220,6 +220,8 @@ return [ PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider::class, Laravel\Passport\PassportServiceProvider::class, Laravel\Tinker\TinkerServiceProvider::class, + Unicodeveloper\DumbPassword\DumbPasswordServiceProvider::class, + Schuppo\PasswordStrength\PasswordStrengthServiceProvider::class, /* diff --git a/database/migrations/2017_08_22_180636_add_secure_password_options.php b/database/migrations/2017_08_22_180636_add_secure_password_options.php new file mode 100644 index 0000000000..f2c0223e1b --- /dev/null +++ b/database/migrations/2017_08_22_180636_add_secure_password_options.php @@ -0,0 +1,36 @@ +boolean('pwd_secure_uncommon')->default('0'); + $table->string('pwd_secure_complexity')->nullable()->default(NULL); + $table->integer('pwd_secure_min')->default('8'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('pwd_secure_uncommon'); + $table->dropColumn('pwd_secure_complexity'); + $table->dropColumn('pwd_secure_min'); + }); + } +} diff --git a/resources/lang/en/admin/settings/general.php b/resources/lang/en/admin/settings/general.php index 09f134feb6..2f6dbcfa95 100644 --- a/resources/lang/en/admin/settings/general.php +++ b/resources/lang/en/admin/settings/general.php @@ -73,6 +73,12 @@ return array( 'php' => 'PHP Version', 'php_gd_info' => 'You must install php-gd to display QR codes, see install instructions.', 'php_gd_warning' => 'PHP Image Processing and GD plugin is NOT installed.', + 'pwd_secure_complexity' => 'Password Complexity', + 'pwd_secure_complexity_help' => 'Select whichever password complexity rules you wish to enforce.', + 'pwd_secure_min' => 'Password minimum characters', + 'pwd_secure_min_help' => 'Minimum permitted value is 5', + 'pwd_secure_uncommon' => 'Prevent common passwords', + 'pwd_secure_uncommon_help' => 'This will disallow users from using common passwords from the top 10,000 passwoerds reported in breaches.', 'qr_help' => 'Enable QR Codes first to set this', 'qr_text' => 'QR Code Text', 'setting' => 'Setting', diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index fd5a589e1a..7a26a6515f 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -36,6 +36,7 @@ return array( "exists" => "The selected :attribute is invalid.", "email_array" => "One or more email addresses is invalid.", "hashed_pass" => "Your current password is incorrect", + 'dumbpwd' => 'That password is too common.', "image" => "The :attribute must be an image.", "in" => "The selected :attribute is invalid.", "integer" => "The :attribute must be an integer.", diff --git a/resources/views/settings/security.blade.php b/resources/views/settings/security.blade.php index 08e7db2faf..5374522abf 100644 --- a/resources/views/settings/security.blade.php +++ b/resources/views/settings/security.blade.php @@ -14,11 +14,6 @@ {{-- Page content --}} @section('content') - {{ Form::open(['method' => 'POST', 'files' => true, 'class' => 'form-horizontal', 'role' => 'form' ]) }} @@ -58,6 +53,68 @@ + +
+
+ {{ Form::label('pwd_secure_min', trans('admin/settings/general.pwd_secure_min')) }} +
+
+ {{ Form::text('pwd_secure_min', Input::old('pwd_secure_min', $setting->pwd_secure_min), array('class' => 'form-control', 'style'=>'width: 50px;')) }} + + {!! $errors->first('pwd_secure_min', ':message') !!} +

+ {{ trans('admin/settings/general.pwd_secure_min_help') }} +

+ + +
+
+ + + +
+
+ {{ Form::label('pwd_secure_text', + trans('admin/settings/general.pwd_secure_uncommon')) }} + +
+
+ {{ Form::checkbox('pwd_secure_uncommon', '1', Input::old('pwd_secure_uncommon', $setting->pwd_secure_uncommon),array('class' => 'minimal')) }} + {{ Form::label('pwd_secure_uncommon', trans('general.yes')) }} + {!! $errors->first('pwd_secure_uncommon', ':message') !!} +

+ {{ trans('admin/settings/general.pwd_secure_uncommon_help') }} +

+
+
+ + + +
+
+ {{ Form::label('pwd_secure_complexity', trans('admin/settings/general.pwd_secure_complexity')) }} +
+
+ + {{ Form::checkbox("pwd_secure_complexity['letters']", 'letters', Input::old('pwd_secure_uncommon', strpos($setting->pwd_secure_complexity, 'letters')!==false), array('class' => 'minimal')) }} + Require at least one letter
+ + {{ Form::checkbox("pwd_secure_complexity['numbers']", 'numbers', Input::old('pwd_secure_uncommon', strpos($setting->pwd_secure_complexity, 'numbers')!==false), array('class' => 'minimal')) }} + Require at least one number
+ + {{ Form::checkbox("pwd_secure_complexity['symbols']", 'symbols', Input::old('pwd_secure_uncommon', strpos($setting->pwd_secure_complexity, 'symbols')!==false), array('class' => 'minimal')) }} + Require at least one symbol
+ + {{ Form::checkbox("pwd_secure_complexity['case_diff']", 'case_diff', Input::old('pwd_secure_uncommon', strpos($setting->pwd_secure_complexity, 'case_diff')!==false), array('class' => 'minimal')) }} + Require at least one uppercase and one lowercase + +

+ {{ trans('admin/settings/general.pwd_secure_complexity_help') }} +

+
+
+ +