mirror of
https://github.com/librenms/librenms.git
synced 2024-09-21 10:28:13 +00:00
New User Management (#9348)
* Rewrite user management. Error management Revert edituser legacy page Connect user permissions button to legacy page for now. Implement user creation Refine form Remove PingCheck.php accidental add :) Fixes for redirection and deletion More fixes: realname accidental validation setting, hide can modify for read-only auths Use a panel to improve style Add icon to panel-title Not allowed to delete own user (at least via the click of a button) Use request validation to reduce complexity of controller. Improve protection against users doing things they should not. Switch to horizontal form and not nearly as wide of layout :) delete without refresh. Fix for buttons Include all users (not just from this auth) Hide the auth column if there is only one auth type Show username if real name isn't set Don't allow creation of demo users via the webui a fix to the lnms user:add command, it didn't set auth_id update edituser.inc.php to current just redirect to users page * Remove TwoFactorTest for now * Update edituser.inc.php * Update .env.dusk.testing * Enable 2fa for 2fa test...
This commit is contained in:
parent
39ff4c7aaf
commit
6e6e54cb98
@ -2,4 +2,3 @@ APP_URL=http://localhost:8000
|
||||
APP_KEY=base64:FSjpEaK3F9HnO40orj7FlbRI0loi1vtB3dVBcB9XaDk=
|
||||
APP_ENV=testing
|
||||
APP_DEBUG=true
|
||||
#DB_CONNECTION=memory
|
||||
|
@ -13,7 +13,7 @@ matrix:
|
||||
- php: 7.3
|
||||
env: SKIP_STYLE_CHECK=1
|
||||
- php: 7.2
|
||||
env: SKIP_UNIT_CHECK=1 BROWSER_TEST=1
|
||||
env: SKIP_UNIT_CHECK=1 BROWSER_TEST=1 CHROME_HEADLESS=1
|
||||
- php: 7.1
|
||||
env: SKIP_STYLE_CHECK=1 EXECUTE_BUILD_DOCS=true
|
||||
|
||||
|
@ -28,6 +28,7 @@ namespace App\Console\Commands;
|
||||
use App\Console\LnmsCommand;
|
||||
use App\Models\User;
|
||||
use Illuminate\Validation\Rule;
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
use LibreNMS\Config;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@ -87,14 +88,18 @@ class AddUserCommand extends LnmsCommand
|
||||
$user = new User([
|
||||
'username' => $this->argument('username'),
|
||||
'level' => $roles[$this->option('role')],
|
||||
'descr' => (string)$this->option('descr'),
|
||||
'email' => (string)$this->option('email'),
|
||||
'realname' => (string)$this->option('full-name'),
|
||||
'descr' => $this->option('descr'),
|
||||
'email' => $this->option('email'),
|
||||
'realname' => $this->option('full-name'),
|
||||
'auth_type' => 'mysql',
|
||||
]);
|
||||
|
||||
$user->setPassword($password);
|
||||
$user->save();
|
||||
|
||||
$user->auth_id = LegacyAuth::get()->getUserid($user->username) ?: $user->user_id;
|
||||
$user->save();
|
||||
|
||||
$this->info(__('commands.user:add.success', ['username' => $user->username]));
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,7 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* TwoFactorController.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Models\UserPref;
|
||||
use Illuminate\Http\Request;
|
||||
@ -72,6 +96,7 @@ class TwoFactorController extends Controller
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function create(Request $request)
|
||||
@ -80,14 +105,14 @@ class TwoFactorController extends Controller
|
||||
'twofactor' => Rule::in('time', 'counter')
|
||||
]);
|
||||
|
||||
$key = \LibreNMS\Authentication\TwoFactor::genKey();
|
||||
$key = TwoFactor::genKey();
|
||||
|
||||
// assume time based
|
||||
$settings = [
|
||||
'key' => $key,
|
||||
'fails' => 0,
|
||||
'last' => 0,
|
||||
'counter' => $request->get('twofactor') == 'counter' ? 0 : false,
|
||||
'counter' => $request->get('twofactortype') == 'counter' ? 0 : false,
|
||||
];
|
||||
|
||||
Session::put('twofactoradd', $settings);
|
||||
@ -95,11 +120,10 @@ class TwoFactorController extends Controller
|
||||
return redirect()->intended();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Request $request)
|
||||
@ -113,7 +137,7 @@ class TwoFactorController extends Controller
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function cancelAdd(Request $request)
|
||||
@ -126,8 +150,8 @@ class TwoFactorController extends Controller
|
||||
/**
|
||||
* @param User $user
|
||||
* @param string $token
|
||||
* @return true
|
||||
* @throws AuthenticationException
|
||||
* return true
|
||||
*/
|
||||
private function checkToken($user, $token)
|
||||
{
|
||||
@ -182,7 +206,7 @@ class TwoFactorController extends Controller
|
||||
private function loadSettings($user)
|
||||
{
|
||||
if (Session::has('twofactoradd')) {
|
||||
return Session::get('twofactoradd');
|
||||
return Session::get('twofactoradd');
|
||||
}
|
||||
|
||||
return UserPref::getPref($user, 'twofactor');
|
||||
@ -190,7 +214,7 @@ class TwoFactorController extends Controller
|
||||
|
||||
private function genUri($user, $settings)
|
||||
{
|
||||
$title = urlencode("Librenms:" . $user->username);
|
||||
$title = "LibreNMS:" . urlencode($user->username);
|
||||
$key = $settings['key'];
|
||||
|
||||
// time based
|
55
app/Http/Controllers/Auth/TwoFactorManagementController.php
Normal file
55
app/Http/Controllers/Auth/TwoFactorManagementController.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* TwoFactorManagementController.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\TwoFactorManagementRequest;
|
||||
use App\Models\User;
|
||||
use App\Models\UserPref;
|
||||
|
||||
class TwoFactorManagementController extends Controller
|
||||
{
|
||||
public function unlock(TwoFactorManagementRequest $request, User $user)
|
||||
{
|
||||
$twofactor = UserPref::getPref($user, 'twofactor');
|
||||
$twofactor['fails'] = 0;
|
||||
|
||||
if (UserPref::setPref($user, 'twofactor', $twofactor)) {
|
||||
return response()->json(['msg' => __('Two-Factor unlocked.')]);
|
||||
}
|
||||
|
||||
return response()->json(['error' => __("Failed to unlock Two-Factor.")]);
|
||||
}
|
||||
|
||||
public function destroy(TwoFactorManagementRequest $request, User $user)
|
||||
{
|
||||
if (UserPref::forgetPref($user, 'twofactor')) {
|
||||
return response()->json(['msg' => __('Two-Factor removed.')]);
|
||||
}
|
||||
|
||||
return response()->json(['error' => __("Failed to remove Two-Factor.")]);
|
||||
}
|
||||
}
|
@ -34,6 +34,8 @@ class DeviceController extends SelectController
|
||||
protected function rules()
|
||||
{
|
||||
return [
|
||||
'access' => 'nullable|in:normal,inverted',
|
||||
'user' => 'nullable|int',
|
||||
'id' => 'nullable|in:device_id,hostname'
|
||||
];
|
||||
}
|
||||
@ -46,6 +48,19 @@ class DeviceController extends SelectController
|
||||
protected function baseQuery($request)
|
||||
{
|
||||
$this->id = $request->get('id', 'device_id');
|
||||
$user_id = $request->get('user');
|
||||
|
||||
// list devices the user does not have access to
|
||||
if ($request->get('access') == 'inverted' && $user_id && $request->user()->isAdmin()) {
|
||||
return Device::query()
|
||||
->select('device_id', 'hostname', 'sysName')
|
||||
->whereNotIn('device_id', function ($query) use ($user_id) {
|
||||
$query->select('device_id')
|
||||
->from('devices_perms')
|
||||
->where('user_id', $user_id);
|
||||
})
|
||||
->orderBy('hostname');
|
||||
}
|
||||
|
||||
return Device::hasAccess($request->user())
|
||||
->select('device_id', 'hostname', 'sysName')
|
||||
|
213
app/Http/Controllers/UserController.php
Normal file
213
app/Http/Controllers/UserController.php
Normal file
@ -0,0 +1,213 @@
|
||||
<?php
|
||||
/**
|
||||
* UserController.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\StoreUserRequest;
|
||||
use App\Http\Requests\UpdateUserRequest;
|
||||
use App\Models\Dashboard;
|
||||
use App\Models\User;
|
||||
use App\Models\UserPref;
|
||||
use Hash;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
use LibreNMS\Config;
|
||||
use Toastr;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('manage', User::class);
|
||||
|
||||
return view('user.index', [
|
||||
'users' => User::orderBy('username')->get(),
|
||||
'multiauth' => User::query()->distinct('auth_type')->count('auth_type') > 1,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->authorize('create', User::class);
|
||||
|
||||
$tmp_user = new User;
|
||||
$tmp_user->can_modify_passwd = LegacyAuth::get()->canUpdatePasswords(); // default to true for new users
|
||||
return view('user.create', [
|
||||
'user' => $tmp_user,
|
||||
'dashboard' => null,
|
||||
'dashboards' => Dashboard::allAvailable($tmp_user)->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param StoreUserRequest $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(StoreUserRequest $request)
|
||||
{
|
||||
$user = $request->only(['username', 'realname', 'email', 'descr', 'level', 'can_modify_passwd']);
|
||||
$user['auth_type'] = LegacyAuth::getType();
|
||||
$user['can_modify_passwd'] = $request->get('can_modify_passwd'); // checkboxes are missing when unchecked
|
||||
|
||||
$user = User::create($user);
|
||||
|
||||
$user->setPassword($request->new_password);
|
||||
$user->auth_id = LegacyAuth::get()->getUserid($user->username) ?: $user->user_id;
|
||||
$this->updateDashboard($user, $request->get('dashboard'));
|
||||
|
||||
if ($user->save()) {
|
||||
Toastr::success(__('User :username created', ['username', $user->username]));
|
||||
return redirect(route('users.index'));
|
||||
}
|
||||
|
||||
Toastr::error(__('Failed to create user'));
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param User $user
|
||||
* @return \Illuminate\Http\Response
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show(User $user)
|
||||
{
|
||||
$this->authorize('view', $user);
|
||||
|
||||
return $user->username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param User $user
|
||||
* @return \Illuminate\Http\Response
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit(User $user)
|
||||
{
|
||||
$this->authorize('update', $user);
|
||||
|
||||
$data = [
|
||||
'user' => $user,
|
||||
'dashboard' => UserPref::getPref($user, 'dashboard'),
|
||||
'dashboards' => Dashboard::allAvailable($user)->get(),
|
||||
];
|
||||
|
||||
if (Config::get('twofactor')) {
|
||||
$lockout_time = Config::get('twofactor_lock');
|
||||
$twofactor = UserPref::getPref($user, 'twofactor');
|
||||
$data['twofactor_enabled'] = isset($twofactor['key']);
|
||||
|
||||
// if enabled and 3 or more failures
|
||||
$last_failure = isset($twofactor['last']) ? (time() - $twofactor['last']) : 0;
|
||||
$data['twofactor_locked'] = isset($twofactor['fails']) && $twofactor['fails'] >= 3 && (!$lockout_time || $last_failure < $lockout_time);
|
||||
}
|
||||
|
||||
return view('user.edit', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param UpdateUserRequest $request
|
||||
* @param User $user
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(UpdateUserRequest $request, User $user)
|
||||
{
|
||||
if ($request->get('new_password') && $user->canSetPassword($request->user())) {
|
||||
$user->setPassword($request->new_password);
|
||||
}
|
||||
|
||||
$user->fill($request->all());
|
||||
$user->can_modify_passwd = $request->get('can_modify_passwd'); // checkboxes are missing when unchecked
|
||||
|
||||
if ($this->updateDashboard($user, $request->get('dashboard'))) {
|
||||
Toastr::success(__('Updated dashboard for :username', ['username' => $user->username]));
|
||||
}
|
||||
|
||||
if ($user->isDirty()) {
|
||||
if ($user->save()) {
|
||||
Toastr::success(__('User :username updated', ['username' => $user->username]));
|
||||
} else {
|
||||
Toastr::error(__('Failed to update user :username', ['username' => $user->username]));
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
||||
return redirect(route('users.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param User $user
|
||||
* @return \Illuminate\Http\Response
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy(User $user)
|
||||
{
|
||||
$this->authorize('delete', $user);
|
||||
|
||||
$user->delete();
|
||||
|
||||
return response()->json(__('User :username deleted.', ['username' => $user->username]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param $dashboard
|
||||
* @return bool
|
||||
*/
|
||||
protected function updateDashboard(User $user, $dashboard)
|
||||
{
|
||||
if ($dashboard) {
|
||||
$existing = UserPref::getPref($user, 'dashboard');
|
||||
if ($dashboard != $existing) {
|
||||
UserPref::setPref($user, 'dashboard', $dashboard);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
45
app/Http/Requests/StoreUserRequest.php
Normal file
45
app/Http/Requests/StoreUserRequest.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
use LibreNMS\Config;
|
||||
|
||||
class StoreUserRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return $this->user()->can('create', User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'username' => [
|
||||
'required',
|
||||
'alpha_dash',
|
||||
'max:255',
|
||||
Rule::unique('users', 'username')->where('auth_type', LegacyAuth::getType()),
|
||||
],
|
||||
'realname' => 'max:64',
|
||||
'email' => 'nullable|email|max:64',
|
||||
'descr' => 'max:30',
|
||||
'level' => 'int',
|
||||
'new_password' => 'required|confirmed|min:' . Config::get('password.min_length', 8),
|
||||
'dashboard' => 'int',
|
||||
];
|
||||
}
|
||||
}
|
45
app/Http/Requests/TwoFactorManagementRequest.php
Normal file
45
app/Http/Requests/TwoFactorManagementRequest.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* TwoFactorManagementRequest.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class TwoFactorManagementRequest extends FormRequest
|
||||
{
|
||||
public function rules()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function authorize()
|
||||
{
|
||||
$user = $this->route()->parameter('user');
|
||||
$auth_user = auth()->user();
|
||||
|
||||
// don't allow admins to bypass security for themselves
|
||||
return $auth_user->isAdmin() && !$user->is($auth_user);
|
||||
}
|
||||
}
|
74
app/Http/Requests/UpdateUserRequest.php
Normal file
74
app/Http/Requests/UpdateUserRequest.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Hash;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use LibreNMS\Config;
|
||||
|
||||
class UpdateUserRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
if ($this->user()->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$user = $this->route('user');
|
||||
if ($user && $this->user()->can('update', $user)) {
|
||||
// normal users cannot edit their level or ability to modify a password
|
||||
unset($this['level'], $this['can_modify_passwd']);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'realname' => 'max:64',
|
||||
'email' => 'nullable|email|max:64',
|
||||
'descr' => 'max:30',
|
||||
'level' => 'int',
|
||||
'old_password' => 'nullable|string',
|
||||
'new_password' => 'nullable|confirmed|min:' . Config::get('password.min_length', 8),
|
||||
'dashboard' => 'int',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*
|
||||
* @param \Illuminate\Validation\Validator $validator
|
||||
* @return void
|
||||
*/
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
// if not an admin and new_password is set, check old password matches
|
||||
if (!$this->user()->isAdmin()) {
|
||||
if ($this->has('new_password')) {
|
||||
if ($this->has('old_password')) {
|
||||
$user = $this->route('user');
|
||||
if ($user && !Hash::check($this->old_password, $user->password)) {
|
||||
$validator->errors()->add('old_password', __('Existing password did not match'));
|
||||
}
|
||||
} else {
|
||||
$validator->errors()->add('old_password', __('The :attribute field is required.', ['attribute' => 'old_password']));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -244,20 +244,16 @@ class PingCheck implements ShouldQueue
|
||||
$device->last_ping = Carbon::now();
|
||||
$device->last_ping_timetaken = isset($data['rtt']) ? $data['rtt'] : 0;
|
||||
|
||||
if ($changed = $device->isDirty('status')) {
|
||||
if ($device->isDirty('status')) {
|
||||
// if changed, update reason
|
||||
$device->status_reason = $device->status ? '' : 'icmp';
|
||||
$type = $device->status ? 'up' : 'down';
|
||||
|
||||
log_event('Device status changed to ' . ucfirst($type) . " from icmp check.", $device->toArray(), $type);
|
||||
|
||||
echo "Device $device->hostname changed status to $type, running alerts\n";
|
||||
}
|
||||
|
||||
$device->save();
|
||||
|
||||
if ($changed) {
|
||||
RunRules($device->device_id);
|
||||
}
|
||||
$device->save(); // only saves if needed (which is every time because of last_ping)
|
||||
|
||||
// add data to rrd
|
||||
data_update($device->toArray(), 'ping-perf', $this->rrd_tags, ['ping' => $device->last_ping_timetaken]);
|
||||
|
@ -19,12 +19,18 @@ class User extends Authenticatable
|
||||
'descr' => '',
|
||||
'realname' => '',
|
||||
'email' => '',
|
||||
'can_modify_passwd' => 0,
|
||||
];
|
||||
protected $dispatchesEvents = [
|
||||
'created' => UserCreated::class,
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'realname' => 'string',
|
||||
'descr' => 'string',
|
||||
'email' => 'string',
|
||||
'can_modify_passwd' => 'integer',
|
||||
];
|
||||
|
||||
// ---- Helper Functions ----
|
||||
|
||||
/**
|
||||
@ -90,6 +96,25 @@ class User extends Authenticatable
|
||||
$this->attributes['password'] = $password ? password_hash($password, PASSWORD_DEFAULT) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given user can set the password for this user
|
||||
*
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function canSetPassword($user)
|
||||
{
|
||||
if ($user && LegacyAuth::get()->canUpdatePasswords()) {
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->is($this) && $this->can_modify_passwd;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- Query scopes ----
|
||||
|
||||
/**
|
||||
@ -111,13 +136,33 @@ class User extends Authenticatable
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Accessors/Mutators ----
|
||||
|
||||
public function setRealnameAttribute($realname)
|
||||
{
|
||||
$this->attributes['realname'] = (string)$realname;
|
||||
}
|
||||
|
||||
public function setDescrAttribute($descr)
|
||||
{
|
||||
$this->attributes['descr'] = (string)$descr;
|
||||
}
|
||||
|
||||
public function setEmailAttribute($email)
|
||||
{
|
||||
$this->attributes['email'] = (string)$email;
|
||||
}
|
||||
|
||||
public function setCanModifyPasswdAttribute($modify)
|
||||
{
|
||||
$this->attributes['can_modify_passwd'] = $modify ? 1 : 0;
|
||||
}
|
||||
|
||||
// ---- Define Relationships ----
|
||||
|
||||
public function devices()
|
||||
{
|
||||
if ($this->hasGlobalRead()) {
|
||||
// $instance = $this->newRelatedInstance('App\Models\Device');
|
||||
// return new HasAll($instance);
|
||||
return Device::query();
|
||||
} else {
|
||||
return $this->belongsToMany('App\Models\Device', 'devices_perms', 'user_id', 'device_id');
|
||||
|
69
app/Policies/UserPolicy.php
Normal file
69
app/Policies/UserPolicy.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class UserPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Determine whether the user can manage users.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function manage(User $user)
|
||||
{
|
||||
return $user->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the user.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @param \App\Models\User $target
|
||||
* @return bool
|
||||
*/
|
||||
public function view(User $user, User $target)
|
||||
{
|
||||
return $user->isAdmin() || $target->is($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create users.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function create(User $user)
|
||||
{
|
||||
return $user->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the user.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @param \App\Models\User $target
|
||||
* @return bool
|
||||
*/
|
||||
public function update(User $user, User $target)
|
||||
{
|
||||
return $user->isAdmin() || $target->is($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the user.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @param \App\Models\User $target
|
||||
* @return bool
|
||||
*/
|
||||
public function delete(User $user, User $target)
|
||||
{
|
||||
return $user->isAdmin();
|
||||
}
|
||||
}
|
@ -2,7 +2,8 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Providers\LegacyUserProvider;
|
||||
use App\Models\User;
|
||||
use App\Policies\UserPolicy;
|
||||
use App\Guards\ApiTokenGuard;
|
||||
use Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
@ -16,7 +17,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
* @var array
|
||||
*/
|
||||
protected $policies = [
|
||||
'App\Model' => 'App\Policies\ModelPolicy',
|
||||
User::class => UserPolicy::class
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -20,6 +20,7 @@ $factory->define(App\Models\User::class, function (Faker\Generator $faker) {
|
||||
static $password;
|
||||
|
||||
return [
|
||||
'auth_type' => 'mysql',
|
||||
'username' => $faker->unique()->userName,
|
||||
'realname' => $faker->name,
|
||||
'email' => $faker->safeEmail,
|
||||
|
@ -131,6 +131,13 @@ $config['unflatten'] = "/usr/bin/unflatten";
|
||||
$config['neato'] = "/usr/bin/neato";
|
||||
$config['sfdp'] = "/usr/bin/sfdp";
|
||||
```
|
||||
### Authentication
|
||||
|
||||
Generic Authentication settings.
|
||||
|
||||
```php
|
||||
$config['password']['min_length'] = 8; // password minimum length for auth that allows user creation
|
||||
```
|
||||
|
||||
### Proxy support
|
||||
|
||||
|
@ -19,7 +19,7 @@ if (! Auth::user()->hasGlobalAdmin()) {
|
||||
$user = User::find($vars['user_id']);
|
||||
$user_data = $user->toArray(); // for compatibility with current code
|
||||
|
||||
echo '<p><h2>'.$user_data['realname']."</h2><a href='edituser/'>Change...</a></p>";
|
||||
echo '<p><h2>'.$user_data['realname']."</h2></p>";
|
||||
// Perform actions if requested
|
||||
if ($vars['action'] == 'deldevperm') {
|
||||
if (dbFetchCell('SELECT COUNT(*) FROM devices_perms WHERE `device_id` = ? AND `user_id` = ?', array($vars['device_id'], $user_data['user_id']))) {
|
||||
@ -246,238 +246,8 @@ if (! Auth::user()->hasGlobalAdmin()) {
|
||||
<button type='submit' class='btn btn-default' name='Submit' value='Add'>Add</button>
|
||||
</form>
|
||||
</div>";
|
||||
} elseif ($vars['user_id'] && $vars['edit']) {
|
||||
if (Auth::user()->isDemo()) {
|
||||
demo_account();
|
||||
} else {
|
||||
if (!empty($vars['new_level'])) {
|
||||
if ($vars['can_modify_passwd'] == 'on') {
|
||||
$vars['can_modify_passwd'] = '1';
|
||||
}
|
||||
|
||||
LegacyAuth::get()->updateUser($vars['user_id'], $vars['new_realname'], $vars['new_level'], $vars['can_modify_passwd'], $vars['new_email']);
|
||||
print_message('User has been updated');
|
||||
if (!empty($vars['new_pass1']) && $vars['new_pass1'] == $vars['new_pass2'] && LegacyAuth::get()->canUpdatePasswords($vars['cur_username'])) {
|
||||
if (LegacyAuth::get()->changePassword($vars['cur_username'], $vars['new_pass1']) == 1) {
|
||||
print_message("User password has been updated");
|
||||
} else {
|
||||
print_error("Password couldn't be updated");
|
||||
}
|
||||
} elseif (!empty($vars['new_pass1']) && $vars['new_pass1'] != $vars['new_pass2']) {
|
||||
print_error("The supplied passwords didn't match so weren't updated");
|
||||
}
|
||||
}
|
||||
|
||||
$users_details = User::find($vars['user_id'])->toArray();
|
||||
if (!empty($users_details)) {
|
||||
if (!empty($vars['dashboard']) && $vars['dashboard'] != $users_details['dashboard']) {
|
||||
set_user_pref('dashboard', $vars['dashboard']);
|
||||
print_message("User default dashboard updated");
|
||||
}
|
||||
echo "<form class='form-horizontal' role='form' method='post' action=''>
|
||||
<input type='hidden' name='user_id' value='".$vars['user_id']."'>
|
||||
<input type='hidden' name='cur_username' value='" . $users_details['username'] . "'>
|
||||
<input type='hidden' name='edit' value='yes'>
|
||||
";
|
||||
if (LegacyAuth::get()->canUpdateUsers() == '1') {
|
||||
if (empty($vars['new_realname'])) {
|
||||
$vars['new_realname'] = $users_details['realname'];
|
||||
}
|
||||
|
||||
if (empty($vars['new_level'])) {
|
||||
$vars['new_level'] = $users_details['level'];
|
||||
}
|
||||
|
||||
if (empty($vars['can_modify_passwd'])) {
|
||||
$vars['can_modify_passwd'] = $users_details['can_modify_passwd'];
|
||||
} elseif ($vars['can_modify_passwd'] == 'on') {
|
||||
$vars['can_modify_passwd'] = '1';
|
||||
}
|
||||
|
||||
if (empty($vars['new_email'])) {
|
||||
$vars['new_email'] = $users_details['email'];
|
||||
}
|
||||
|
||||
echo "
|
||||
<div class='form-group'>
|
||||
<label for='new_realname' class='col-sm-2 control-label'>Realname</label>
|
||||
<div class='col-sm-4'>
|
||||
<input name='new_realname' class='form-control input-sm' value='".$vars['new_realname']."'>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<label for='new_email' class='col-sm-2 control-label'>Email</label>
|
||||
<div class='col-sm-4'>
|
||||
<input name='new_email' class='form-control input-sm' value='".$vars['new_email']."'>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<label for='new_level' class='col-sm-2 control-label'>Level</label>
|
||||
<div class='col-sm-4'>
|
||||
<select name='new_level' class='form-control input-sm'>
|
||||
<option value='1'";
|
||||
if ($vars['new_level'] == '1') {
|
||||
echo 'selected';
|
||||
} echo ">Normal User</option>
|
||||
<option value='5'";
|
||||
if ($vars['new_level'] == '5') {
|
||||
echo 'selected';
|
||||
} echo ">Global Read</option>
|
||||
<option value='10'";
|
||||
if ($vars['new_level'] == '10') {
|
||||
echo 'selected';
|
||||
} echo ">Administrator</option>
|
||||
<option value='11'";
|
||||
if ($vars['new_level'] == '11') {
|
||||
echo 'selected';
|
||||
} echo ">Demo account</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
</div>
|
||||
</div>";
|
||||
|
||||
if (LegacyAuth::get()->canUpdatePasswords($users_details['username'])) {
|
||||
echo "
|
||||
<div class='form-group'>
|
||||
<label for='new_pass1' class='col-sm-2 control-label'>Password</label>
|
||||
<div class='col-sm-4'>
|
||||
<input type='password' name='new_pass1' class='form-control input-sm' value='". $vars['new_pass1'] ."'>
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<label for='new_pass2' class='col-sm-2 control-label'>Confirm Password</label>
|
||||
<div class='col-sm-4'>
|
||||
<input type='password' name='new_pass2' class='form-control input-sm' value='". $vars['new_pass2'] ."'>
|
||||
</div>
|
||||
</div>
|
||||
";
|
||||
}
|
||||
|
||||
echo "<div class='form-group'>
|
||||
<div class='col-sm-6'>
|
||||
<div class='checkbox'>
|
||||
<label>
|
||||
<input type='checkbox' ";
|
||||
if ($vars['can_modify_passwd'] == '1') {
|
||||
echo "checked='checked'";
|
||||
} echo " name='can_modify_passwd'> Allow the user to change their password.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
</div>
|
||||
</div>
|
||||
";
|
||||
}
|
||||
echo "
|
||||
<div class='form-group'>
|
||||
<label for='dashboard' class='col-sm-2 control-label'>Dashboard</label>
|
||||
<div class='col-sm-4'><select class='form-control' name='dashboard'>";
|
||||
foreach (get_dashboards($vars['user_id']) as $dash) {
|
||||
echo "<option value='".$dash['dashboard_id']."'".($dash['default'] ? ' selected' : '').">".$dash['username'].':'.$dash['dashboard_name']."</option>";
|
||||
}
|
||||
echo "</select>
|
||||
</div>
|
||||
</div>
|
||||
<button type='submit' class='btn btn-default'>Update User</button>
|
||||
</form>";
|
||||
|
||||
if ($config['twofactor']) {
|
||||
if ($vars['twofactorremove']) {
|
||||
if (set_user_pref('twofactor', array(), $vars['user_id'])) {
|
||||
echo "<div class='alert alert-success'>TwoFactor credentials removed.</div>";
|
||||
} else {
|
||||
echo "<div class='alert alert-danger'>Couldnt remove user's TwoFactor credentials.</div>";
|
||||
}
|
||||
}
|
||||
|
||||
if ($vars['twofactorunlock']) {
|
||||
$twofactor = get_user_pref('twofactor', array(), $vars['user_id']);
|
||||
$twofactor['fails'] = 0;
|
||||
if (set_user_pref('twofactor', $twofactor, $vars['user_id'])) {
|
||||
echo "<div class='alert alert-success'>User unlocked.</div>";
|
||||
} else {
|
||||
echo "<div class='alert alert-danger'>Couldnt reset user's TwoFactor failures.</div>";
|
||||
}
|
||||
}
|
||||
echo "<br/><div class='well'><h3>Two-Factor Authentication</h3>";
|
||||
$twofactor = get_user_pref('twofactor', array(), $vars['user_id']);
|
||||
if ($twofactor['fails'] >= 3 && (!$config['twofactor_lock'] || (time() - $twofactor['last']) < $config['twofactor_lock'])) {
|
||||
echo "<form class='form-horizontal' role='form' method='post' action=''>
|
||||
<input type='hidden' name='user_id' value='".$vars['user_id']."'>
|
||||
<input type='hidden' name='edit' value='yes'>
|
||||
<div class='form-group'>
|
||||
<label for='twofactorunlock' class='col-sm-2 control-label'>User exceeded failures</label>
|
||||
<input type='hidden' name='twofactorunlock' value='1'>
|
||||
<button type='submit' class='btn btn-default'>Unlock</button>
|
||||
</div>
|
||||
</form>";
|
||||
}
|
||||
|
||||
if ($twofactor['key']) {
|
||||
echo "<form class='form-horizontal' role='form' method='post' action=''>
|
||||
<input type='hidden' name='user_id' value='".$vars['user_id']."'>
|
||||
<input type='hidden' name='edit' value='yes'>
|
||||
<input type='hidden' name='twofactorremove' value='1'>
|
||||
<button type='submit' class='btn btn-danger'>Disable TwoFactor</button>
|
||||
</form>
|
||||
</div>";
|
||||
} else {
|
||||
echo '<p>No TwoFactor key generated for this user, Nothing to do.</p>';
|
||||
}
|
||||
}//end if
|
||||
} else {
|
||||
print_error('Error getting user details');
|
||||
}//end if !empty($users_details)
|
||||
}//end if
|
||||
} else {
|
||||
$userlist = User::thisAuth()->get();
|
||||
|
||||
echo '<h3>Select a user to edit</h3>';
|
||||
|
||||
echo "<form method='post' action='' class='form-horizontal' role='form'>
|
||||
<input type='hidden' value='edituser' name='page'>
|
||||
<div class='form-group'>
|
||||
<label for='user_id' class='col-sm-2 control-label'>User</label>
|
||||
<div class='col-sm-4'>
|
||||
<select name='user_id' class='form-control input-sm'>";
|
||||
foreach ($userlist as $userentry) {
|
||||
switch ($userentry->level) {
|
||||
case "10":
|
||||
$userlevel = 'admin';
|
||||
break;
|
||||
case "11":
|
||||
$userlevel = 'demo';
|
||||
break;
|
||||
default:
|
||||
$userlevel = '';
|
||||
}
|
||||
if (empty($userlevel)) {
|
||||
$userlevel=$userentry->auth_type;
|
||||
} elseif (!empty($userentry->auth_type)) {
|
||||
$userlevel.= ", ".$userentry->auth_type;
|
||||
}
|
||||
if (!empty($userlevel)) {
|
||||
$userlevel=" ($userlevel)";
|
||||
}
|
||||
|
||||
echo "<option value='".$userentry->user_id."'>".$userentry->username.$userlevel.'</option>';
|
||||
}
|
||||
|
||||
echo "</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<div class='col-sm-offset-2 col-sm-3'>
|
||||
<button type='submit' name='Submit' class='btn btn-default'>Edit Permissions</button> / <button type='submit' name='edit' value='user' class='btn btn-default'>Edit User</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>";
|
||||
echo '<script>window.location.replace("' . url('users') . '");</script>';
|
||||
}//end if
|
||||
}//end if
|
||||
|
||||
|
@ -124,15 +124,15 @@ if (LegacyAuth::user()->isDemoUser()) {
|
||||
<div class="form-group">
|
||||
<label for="twofactortype" class="col-sm-2 control-label">TwoFactor Type</label>
|
||||
<div class="col-sm-4">
|
||||
<select name="twofactortype" class="select">
|
||||
<select name="twofactortype" class="select form-control">
|
||||
<option value="time">Time Based (TOTP)</option>
|
||||
<option value="counter">Counter Based (HOTP)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-1">
|
||||
<button class="btn btn-default" type="submit">Generate TwoFactor Secret Key</button>
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-default" type="submit" id="twofactor-generate">Generate TwoFactor Secret Key</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>';
|
||||
@ -159,16 +159,16 @@ foreach (get_dashboards() as $dash) {
|
||||
echo "
|
||||
<option value='".$dash['dashboard_id']."'".($dash['default'] ? ' selected' : '').">".display($dash['username']).':'.display($dash['dashboard_name'])."</option>";
|
||||
}
|
||||
echo "
|
||||
echo '
|
||||
</select>
|
||||
<br>
|
||||
<center><button type='submit' class='btn btn-default'>Update Dashboard</button></center>
|
||||
</div>
|
||||
<div class='col-sm-6'></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2"><button type="submit" class="btn btn-default">Update Dashboard</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>";
|
||||
</div>';
|
||||
|
||||
|
||||
echo "<h3>Add schedule notes to devices notes</h3>
|
||||
|
@ -656,14 +656,8 @@ if (Auth::user()->hasGlobalAdmin()) {
|
||||
<li role="presentation" class="divider"></li>
|
||||
|
||||
<?php if (Auth::user()->hasGlobalAdmin()) {
|
||||
if (LegacyAuth::get()->canManageUsers()) {
|
||||
echo('
|
||||
<li><a href="adduser/"><i class="fa fa-user-plus fa-fw fa-lg" aria-hidden="true"></i> Add User</a></li>
|
||||
<li><a href="deluser/"><i class="fa fa-user-times fa-fw fa-lg" aria-hidden="true"></i> Remove User</a></li>
|
||||
');
|
||||
}
|
||||
echo('
|
||||
<li><a href="edituser/"><i class="fa fa-user-circle-o fa-fw fa-lg" aria-hidden="true"></i> Edit User</a></li>
|
||||
<li><a href="' . route('users.index') . '"><i class="fa fa-user-circle-o fa-fw fa-lg" aria-hidden="true"></i> Manage Users</a></li>
|
||||
<li><a href="authlog/"><i class="fa fa-shield fa-fw fa-lg" aria-hidden="true"></i> Auth History</a></li>
|
||||
<li role="presentation" class="divider"></li> ');
|
||||
echo('
|
||||
|
@ -358,11 +358,7 @@
|
||||
<li><a href="{{ url('settings') }}"><i class="fa fa-cogs fa-fw fa-lg" aria-hidden="true"></i> Global Settings</a></li>
|
||||
<li><a href="{{ url('validate') }}"><i class="fa fa-check-circle fa-fw fa-lg" aria-hidden="true"></i> Validate Config</a></li>
|
||||
<li role="presentation" class="divider"></li>
|
||||
@if(\LibreNMS\Authentication\LegacyAuth::get()->canManageUsers())
|
||||
<li><a href="{{ url('adduser') }}"><i class="fa fa-user-plus fa-fw fa-lg" aria-hidden="true"></i> Add User</a></li>
|
||||
<li><a href="{{ url('deluser') }}"><i class="fa fa-user-times fa-fw fa-lg" aria-hidden="true"></i> Remove User</a></li>
|
||||
@endif
|
||||
<li><a href="{{ url('edituser') }}"><i class="fa fa-user-circle-o fa-fw fa-lg" aria-hidden="true"></i> Edit User</a></li>
|
||||
<li><a href="{{ route('users.index') }}"><i class="fa fa-user-circle-o fa-fw fa-lg" aria-hidden="true"></i> Manage Users</a></li>
|
||||
<li><a href="{{ url('authlog') }}"><i class="fa fa-shield fa-fw fa-lg" aria-hidden="true"></i> Auth History</a></li>
|
||||
<li role="presentation" class="divider"></li>
|
||||
<li class="dropdown-submenu">
|
||||
|
28
resources/views/user/create.blade.php
Normal file
28
resources/views/user/create.blade.php
Normal file
@ -0,0 +1,28 @@
|
||||
@extends('layouts.librenmsv1')
|
||||
|
||||
@section('title', __('Create User'))
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<form action="users" method="POST" role="form" class="form-horizontal col-sm-offset-3 col-sm-6">
|
||||
<legend>@lang('Create User')</legend>
|
||||
|
||||
<div class="form-group @if($errors->has('username')) has-error @endif">
|
||||
<label for="realname" class="control-label col-sm-3">@lang('Username')</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" id="username" name="username" value="{{ old('username', $user->username) }}">
|
||||
<span class="help-block">{{ $errors->first('username') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@include('user.form')
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-9 col-sm-offset-3">
|
||||
<button type="submit" class="btn btn-primary">@lang('Save')</button>
|
||||
<a type="button" class="btn btn-danger" href="{{ route('users.index') }}">@lang('Cancel')</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
91
resources/views/user/edit.blade.php
Normal file
91
resources/views/user/edit.blade.php
Normal file
@ -0,0 +1,91 @@
|
||||
@extends('layouts.librenmsv1')
|
||||
|
||||
@section('title', __('Edit User'))
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<form action="users/{{ $user->user_id }}" method="POST" role="form" class="form-horizontal col-sm-offset-3 col-sm-6">
|
||||
<legend>@lang('Edit User'): {{ $user->username }}</legend>
|
||||
{{ method_field('PUT') }}
|
||||
|
||||
@include('user.form')
|
||||
|
||||
@config('twofactor')
|
||||
<br/>
|
||||
<div class="panel panel-default col-sm-offset-3">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Two-Factor Authentication</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
@if($twofactor_enabled)
|
||||
@if($twofactor_locked)
|
||||
<div class="form-group" id="twofactor-unlock-form">
|
||||
<button type="button" id="twofactor-unlock" class="btn btn-default col-sm-4 col-sm-offset-1">@lang('Unlock')</button>
|
||||
<label for="twofactor-unlock" class="col-sm-7 control-label">@lang('User exceeded failures')</label>
|
||||
</div>
|
||||
@endif
|
||||
<div class="form-group">
|
||||
<button type="button" id="twofactor-disable" class="btn btn-danger col-sm-offset-1">@lang('Disable TwoFactor')</button>
|
||||
</div>
|
||||
@else
|
||||
<p>@lang('No TwoFactor key generated for this user, Nothing to do.')</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endconfig
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-9 col-sm-offset-3">
|
||||
<button type="submit" class="btn btn-primary">@lang('Save')</button>
|
||||
<a type="button" class="btn btn-danger" href="{{ route('users.index') }}">@lang('Cancel')</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('javascript')
|
||||
<script type="application/javascript">
|
||||
$(document).ready(function () {
|
||||
$('#twofactor-unlock').click(function () {
|
||||
console.log('unlock');
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '{{ route('2fa.unlock', ['user' => $user->user_id]) }}',
|
||||
dataType: "json",
|
||||
success: function(data){
|
||||
if (data.status === 'ok') {
|
||||
$('#twofactor-unlock-form').remove();
|
||||
toastr.success('@lang('Unlocked Two Factor.')');
|
||||
} else {
|
||||
toastr.error('@lang('Failed to unlock Two Factor')<br />' + data.message);
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
toastr.error('@lang('Failed to unlock Two Factor')');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#twofactor-disable').click(function () {
|
||||
$.ajax({
|
||||
type: 'DELETE',
|
||||
url: '{{ route('2fa.delete', ['user' => $user->user_id]) }}',
|
||||
dataType: "json",
|
||||
success: function(data){
|
||||
if (data.status === 'ok') {
|
||||
toastr.success('@lang('Removed Two Factor.')');
|
||||
} else {
|
||||
toastr.error('@lang('Failed to remove Two Factor')<br />' + data.message);
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
toastr.error('@lang('Failed to remove Two Factor')');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
81
resources/views/user/form.blade.php
Normal file
81
resources/views/user/form.blade.php
Normal file
@ -0,0 +1,81 @@
|
||||
<div class="form-group {{ $errors->has('realname') ? 'has-error' : '' }}">
|
||||
<label for="realname" class="control-label col-sm-3 text-nowrap">@lang('Real Name')</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" id="realname" name="realname" value="{{ old('realname', $user->realname) }}">
|
||||
<span class="help-block">{{ $errors->first('realname') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group @if($errors->has('email')) has-error @endif">
|
||||
<label for="email" class="control-label col-sm-3">@lang('Email')</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" id="email" name="email" value="{{ old('email', $user->email) }}">
|
||||
<span class="help-block">{{ $errors->first('email') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group @if($errors->has('descr')) has-error @endif">
|
||||
<label for="descr" class="control-label col-sm-3">@lang('Description')</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" id="descr" name="descr" value="{{ old('descr', $user->descr) }}">
|
||||
<span class="help-block">{{ $errors->first('descr') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@can('admin')
|
||||
<div class="form-group @if($errors->has('level')) has-error @endif">
|
||||
<label for="level" class="control-label col-sm-3">@lang('Level')</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-control" id="level" name="level">
|
||||
<option value="1">@lang('Normal')</option>
|
||||
<option value="5" @if(old('level', $user->level) == 5) selected @endif>@lang('Global Read')</option>
|
||||
<option value="10" @if(old('level', $user->level) == 10) selected @endif>@lang('Admin')</option>
|
||||
@if(old('level', $user->level) == 11)<option value="11" selected>@lang('Demo')</option>@endif
|
||||
</select>
|
||||
<span class="help-block">{{ $errors->first('level') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
|
||||
<div class="form-group @if($errors->has('dashboard')) has-error @endif">
|
||||
<label for="dashboard" class="control-label col-sm-3">@lang('Dashboard')</label>
|
||||
<div class="col-sm-9">
|
||||
<select id="dashboard" name="dashboard" class="form-control">
|
||||
@foreach($dashboards as $dash)
|
||||
<option value="{{ $dash->dashboard_id }}" @if(old('dashboard', $dashboard) == $dash->dashboard_id) selected @endif>{{ $dash->dashboard_name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<span class="help-block">{{ $errors->first('dashboard') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($user->canSetPassword(auth()->user()))
|
||||
<div class="form-group @if($errors->hasAny(['old_password', 'new_password', 'new_password_confirmation'])) has-error @endif">
|
||||
<label for="password" class="control-label col-sm-3">@lang('Password')</label>
|
||||
<div class="col-sm-9">
|
||||
@cannot('admin')
|
||||
<input type="password" class="form-control" id="old_password" name="old_password" placeholder="@lang('Current Password')">
|
||||
@endcannot
|
||||
<input type="password" autocomplete="off" class="form-control" id="new_password" name="new_password" placeholder="@lang('New Password')">
|
||||
<input type="password" autocomplete="off" class="form-control" id="new_password_confirmation" name="new_password_confirmation" placeholder="@lang('Confirm Password')">
|
||||
<span class="help-block">
|
||||
@foreach($errors->get('*password*') as $error)
|
||||
{{ implode(' ', $error) }}
|
||||
@endforeach
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(\LibreNMS\Authentication\LegacyAuth::get()->canUpdatePasswords())
|
||||
<div class="form-group @if($errors->has('can_modify_passwd')) has-error @endif">
|
||||
<div class="col-sm-9 col-sm-offset-3">
|
||||
<div class="checkbox">
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" id="can_modify_passwd" name="can_modify_passwd" @if(old('can_modify_passwd', $user->can_modify_passwd)) checked @endif> @lang('Can Modify Password')
|
||||
</label>
|
||||
</div>
|
||||
<span class="help-block">{{ $errors->first('can_modify_passwd') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
123
resources/views/user/index.blade.php
Normal file
123
resources/views/user/index.blade.php
Normal file
@ -0,0 +1,123 @@
|
||||
@extends('layouts.librenmsv1')
|
||||
|
||||
@section('title', __('Manage Users'))
|
||||
|
||||
@section('content')
|
||||
|
||||
<div id="manage-users-panel" class="panel panel-default">
|
||||
<div class="panel-heading"><h4 class="panel-title"><i class="fa fa-user-circle-o fa-fw fa-lg" aria-hidden="true"></i> @lang('Manage Users')</h4></div>
|
||||
<div class="panel-body">
|
||||
<div class="table-responsive">
|
||||
<table id="users" class="table table-bordered table-condensed" style="display: none;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="user_id" data-visible="false" data-identifier="true" data-type="numeric">@lang('ID')</th>
|
||||
<th data-column-id="username">@lang('Username')</th>
|
||||
<th data-column-id="realname">@lang('Real Name')</th>
|
||||
<th data-column-id="level" data-formatter="level" data-type="numeric">@lang('Access')</th>
|
||||
<th data-column-id="auth_type" data-visible="{{ $multiauth ? 'true' : 'false' }}">@lang('Auth')</th>
|
||||
<th data-column-id="email">@lang('Email')</th>
|
||||
<th data-column-id="descr">@lang('Description')</th>
|
||||
<th data-column-id="action" data-formatter="actions" data-sortable="false" data-searchable="false">@lang('Actions')</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="users_rows">
|
||||
@foreach($users as $user)
|
||||
<tr>
|
||||
<td>{{ $user->user_id }}</td>
|
||||
<td>{{ $user->username }}</td>
|
||||
<td>{{ $user->realname }}</td>
|
||||
<td>{{ $user->level }}</td>
|
||||
<td>{{ $user->auth_type }}</td>
|
||||
<td>{{ $user->email }}</td>
|
||||
<td>{{ $user->descr }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('javascript')
|
||||
<script type="application/javascript">
|
||||
$(document).ready(function(){
|
||||
var user_grid = $("#users");
|
||||
user_grid.bootgrid({
|
||||
formatters: {
|
||||
actions: function (column, row) {
|
||||
var edit_button = '<form action="users/' + row['user_id'] + '/edit" method="GET">' +
|
||||
'<button type="submit" title="@lang('Edit')" class="btn btn-sm btn-warning"><i class="fa fa-pencil"></i></button>' +
|
||||
'</form> ';
|
||||
|
||||
var delete_button = '<button type="button" title="@lang('Delete')" class="btn btn-sm btn-danger" onclick="return delete_user(' + row['user_id'] + ', \'' + row['username'] + '\');">' +
|
||||
'<i class="fa fa-trash"></i></button> ';
|
||||
|
||||
var manage_button = '<form action="edituser/" method="GET"';
|
||||
|
||||
if (row['level'] >= 5) {
|
||||
manage_button += ' style="visibility:hidden;"'
|
||||
}
|
||||
|
||||
manage_button += '><input type="hidden" name="user_id" value="' + row['user_id'] +
|
||||
'"><button type="submit" title="@lang('Manage Access')" class="btn btn-sm btn-primary"><i class="fa fa-tasks"></i></button>' +
|
||||
'</form> ';
|
||||
|
||||
var output = manage_button + edit_button;
|
||||
if ('{{ Auth::id() }}' != row['user_id']) {
|
||||
output += delete_button;
|
||||
}
|
||||
|
||||
return output
|
||||
},
|
||||
level: function (column, row) {
|
||||
var level = row[column.id];
|
||||
if (level == 10) {
|
||||
return '@lang('Admin')';
|
||||
} else if (level == 5) {
|
||||
return '@lang('Global Read')';
|
||||
} else if (level == 11) {
|
||||
return '@lang('Demo')';
|
||||
}
|
||||
|
||||
return '@lang('Normal')';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@if(\LibreNMS\Authentication\LegacyAuth::get()->canManageUsers())
|
||||
$('.actionBar').append('<div class="pull-left"><a href="users/create" type="button" class="btn btn-primary">@lang('Add User')</a></div>');
|
||||
@endif
|
||||
|
||||
user_grid.css('display', 'table'); // done loading, show
|
||||
});
|
||||
|
||||
function delete_user(user_id, username)
|
||||
{
|
||||
if (confirm('@lang('Are you sure you want to delete ')' + username + '?')) {
|
||||
$.ajax({
|
||||
url: 'users/' + user_id,
|
||||
type: 'DELETE',
|
||||
success: function (msg) {
|
||||
$("#users").bootgrid("remove", [user_id]);
|
||||
toastr.success(msg);
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('@lang('The user could not be deleted')');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@section('css')
|
||||
<style>
|
||||
#manage-users-panel .panel-title { font-size: 18px; }
|
||||
#users form { display:inline; }
|
||||
</style>
|
||||
@endsection
|
@ -28,11 +28,18 @@ Route::group(['middleware' => ['auth', '2fa'], 'guard' => 'auth'], function () {
|
||||
Route::permanentRedirect('poll-log', 'pollers/tab=log/');
|
||||
|
||||
// Two Factor Auth
|
||||
Route::get('2fa', 'TwoFactorController@showTwoFactorForm')->name('2fa.form');
|
||||
Route::post('2fa', 'TwoFactorController@verifyTwoFactor')->name('2fa.verify');
|
||||
Route::post('2fa/add', 'TwoFactorController@create');
|
||||
Route::post('2fa/cancel', 'TwoFactorController@cancelAdd')->name('2fa.cancel');
|
||||
Route::post('2fa/remove', 'TwoFactorController@destroy');
|
||||
Route::group(['prefix' => '2fa', 'namespace' => 'Auth'], function () {
|
||||
Route::get('', 'TwoFactorController@showTwoFactorForm')->name('2fa.form');
|
||||
Route::post('', 'TwoFactorController@verifyTwoFactor')->name('2fa.verify');
|
||||
Route::post('add', 'TwoFactorController@create');
|
||||
Route::post('cancel', 'TwoFactorController@cancelAdd')->name('2fa.cancel');
|
||||
Route::post('remove', 'TwoFactorController@destroy')->name('2fa.remove');
|
||||
|
||||
Route::post('{user}/unlock', 'TwoFactorManagementController@unlock')->name('2fa.unlock');
|
||||
Route::delete('{user}', 'TwoFactorManagementController@destroy')->name('2fa.delete');
|
||||
});
|
||||
|
||||
Route::resource('users', 'UserController');
|
||||
|
||||
// Ajax routes
|
||||
Route::group(['prefix' => 'ajax'], function () {
|
||||
|
@ -3,9 +3,12 @@
|
||||
namespace LibreNMS\Tests\Browser;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\UserPref;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Laravel\Dusk\Browser;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Tests\Browser\Pages\LoginPage;
|
||||
use LibreNMS\Tests\Browser\Pages\TwoFactorPage;
|
||||
use LibreNMS\Tests\DuskTestCase;
|
||||
|
||||
/**
|
||||
@ -28,11 +31,50 @@ class LoginTest extends DuskTestCase
|
||||
'password' => password_hash($password, PASSWORD_DEFAULT)
|
||||
]);
|
||||
|
||||
$browser->visit(new LoginPage())
|
||||
->type('username', $user->username)
|
||||
->type('password', 'wrong_password')
|
||||
->press('@login')
|
||||
->assertPathIs('/login')
|
||||
->type('username', $user->username)
|
||||
->type('password', $password)
|
||||
->press('@login')
|
||||
->assertPathIs('/')
|
||||
->logout();
|
||||
|
||||
$user->delete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function test2faLogin()
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$password = 'another_password';
|
||||
$user = factory(User::class)->create([
|
||||
'password' => password_hash($password, PASSWORD_DEFAULT)
|
||||
]);
|
||||
Config::set('twofactor', true, true); // set to db
|
||||
UserPref::setPref($user, 'twofactor', [
|
||||
'key' => '5P3FLXBX7NU3ZBFOTWZL2GL5MKFEWBOA', // known key: 634456, 613687, 064292
|
||||
'fails' => 0,
|
||||
'last' => 0,
|
||||
'counter' => 1,
|
||||
]);
|
||||
|
||||
$browser->visit(new LoginPage())
|
||||
->type('username', $user->username)
|
||||
->type('password', $password)
|
||||
->press('#login')
|
||||
->assertPathIs('/');
|
||||
->on(new TwoFactorPage())
|
||||
->assertFocused('@input')
|
||||
->keys('@input', '999999', '{enter}') // try the wrong code first
|
||||
->assertPathIs('/2fa')
|
||||
->keys('@input', '634456', '{enter}')
|
||||
->assertPathIs('/')
|
||||
->logout();
|
||||
|
||||
$user->delete();
|
||||
});
|
||||
|
@ -35,7 +35,7 @@ class LoginPage extends Page
|
||||
public function elements()
|
||||
{
|
||||
return [
|
||||
'@element' => '#selector',
|
||||
'@login' => '#login',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
65
tests/Browser/Pages/PreferencesPage.php
Normal file
65
tests/Browser/Pages/PreferencesPage.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* PreferencesPage.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2019 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Tests\Browser\Pages;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
|
||||
class PreferencesPage extends Page
|
||||
{
|
||||
/**
|
||||
* Get the URL for the page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function url()
|
||||
{
|
||||
return '/preferences';
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the browser is on the page.
|
||||
*
|
||||
* @param Browser $browser
|
||||
* @return void
|
||||
*/
|
||||
public function assert(Browser $browser)
|
||||
{
|
||||
$browser->assertPathIs($this->url());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element shortcuts for the page.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function elements()
|
||||
{
|
||||
return [
|
||||
'@type' => 'select[name=twofactortype]',
|
||||
'@generate' => '#twofactor-generate',
|
||||
];
|
||||
}
|
||||
}
|
64
tests/Browser/Pages/TwoFactorPage.php
Normal file
64
tests/Browser/Pages/TwoFactorPage.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* TwoFactorPage.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2019 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Tests\Browser\Pages;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
|
||||
class TwoFactorPage extends Page
|
||||
{
|
||||
/**
|
||||
* Get the URL for the page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function url()
|
||||
{
|
||||
return '/2fa';
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the browser is on the page.
|
||||
*
|
||||
* @param Browser $browser
|
||||
* @return void
|
||||
*/
|
||||
public function assert(Browser $browser)
|
||||
{
|
||||
$browser->assertPathIs($this->url());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element shortcuts for the page.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function elements()
|
||||
{
|
||||
return [
|
||||
'@input' => '#twofactor',
|
||||
];
|
||||
}
|
||||
}
|
@ -29,12 +29,17 @@ abstract class DuskTestCase extends BaseTestCase
|
||||
*/
|
||||
protected function driver()
|
||||
{
|
||||
$arguments = [
|
||||
'--disable-gpu',
|
||||
];
|
||||
|
||||
if (env('CHROME_HEADLESS')) {
|
||||
$arguments[] = '--headless';
|
||||
}
|
||||
|
||||
return RemoteWebDriver::create('http://localhost:9515', DesiredCapabilities::chrome()->setCapability(
|
||||
ChromeOptions::CAPABILITY,
|
||||
(new ChromeOptions)->addArguments([
|
||||
'--disable-gpu',
|
||||
'--headless'
|
||||
])
|
||||
(new ChromeOptions)->addArguments($arguments)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user