diff --git a/LibreNMS/Authentication/TwoFactor.php b/LibreNMS/Authentication/TwoFactor.php index 564264fc02..02f412ab45 100644 --- a/LibreNMS/Authentication/TwoFactor.php +++ b/LibreNMS/Authentication/TwoFactor.php @@ -55,7 +55,7 @@ class TwoFactor /** * Base32 Decoding dictionary */ - private static $base32 = array( + private static $base32 = [ "A" => 0, "B" => 1, "C" => 2, @@ -88,7 +88,7 @@ class TwoFactor "5" => 29, "6" => 30, "7" => 31 - ); + ]; /** * Base32 Encoding dictionary @@ -195,4 +195,20 @@ class TwoFactor (ord($hash[$offset + 3]) & 0xff)) % pow(10, self::OTP_SIZE); return str_pad($truncated, self::OTP_SIZE, '0', STR_PAD_LEFT); } + + /** + * Generate 2fa URI + * @param string $username + * @param string $key + * @param bool $counter if type is counter (false for time based) + * @return string + */ + public static function generateUri($username, $key, $counter = false) + { + $title = "LibreNMS:" . urlencode($username); + + return $counter ? + "otpauth://hotp/$title?issuer=LibreNMS&counter=1&secret=$key" : // counter based + "otpauth://totp/$title?issuer=LibreNMS&secret=$key"; // time based + } } diff --git a/app/Http/Controllers/Auth/TwoFactorController.php b/app/Http/Controllers/Auth/TwoFactorController.php index 53f1a02888..e261f8c9df 100644 --- a/app/Http/Controllers/Auth/TwoFactorController.php +++ b/app/Http/Controllers/Auth/TwoFactorController.php @@ -89,7 +89,7 @@ class TwoFactorController extends Controller return view('auth.2fa')->with([ 'key' => $twoFactorSettings['key'], - 'uri' => $this->genUri($request->user(), $twoFactorSettings), + 'uri' => TwoFactor::generateUri($request->user()->username, $twoFactorSettings['key'], $twoFactorSettings['counter'] !== false), ])->withErrors($errors); } @@ -211,18 +211,4 @@ class TwoFactorController extends Controller return UserPref::getPref($user, 'twofactor'); } - - private function genUri($user, $settings) - { - $title = "LibreNMS:" . urlencode($user->username); - $key = $settings['key']; - - // time based - if ($settings['counter'] === false) { - return "otpauth://totp/$title?issuer=LibreNMS&secret=$key"; - } - - // counter based - return "otpauth://hotp/$title?issuer=LibreNMS&counter=1&secret=$key"; - } } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 9dcaa53135..edf8d2d129 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -30,15 +30,18 @@ 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; +use URL; class UserController extends Controller { + public function __construct() + { + $this->middleware('deny-demo'); + } + /** * Display a listing of the resource. * @@ -159,9 +162,8 @@ class UserController extends Controller } $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'))) { + if ($request->has('dashboard') && $this->updateDashboard($user, $request->get('dashboard'))) { Toastr::success(__('Updated dashboard for :username', ['username' => $user->username])); } @@ -174,7 +176,7 @@ class UserController extends Controller } } - return redirect(route('users.index')); + return redirect(route(str_contains(URL::previous(), 'preferences') ? 'preferences.index' : 'users.index')); } /** diff --git a/app/Http/Controllers/UserPreferencesController.php b/app/Http/Controllers/UserPreferencesController.php new file mode 100644 index 0000000000..feff231a9f --- /dev/null +++ b/app/Http/Controllers/UserPreferencesController.php @@ -0,0 +1,109 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2019 Tony Murray + * @author Tony Murray + */ + +namespace App\Http\Controllers; + +use App\Models\Dashboard; +use App\Models\Device; +use App\Models\UserPref; +use Illuminate\Http\Request; +use Illuminate\Validation\Rule; +use LibreNMS\Authentication\LegacyAuth; +use LibreNMS\Authentication\TwoFactor; +use LibreNMS\Config; +use Session; + +class UserPreferencesController extends Controller +{ + private $valid_prefs = [ + 'dashboard' => 'required|integer', + 'add_schedule_note_to_device' => 'required|integer', + 'locale' => 'required|in:en,ru', + ]; + + public function __construct() + { + $this->middleware('deny-demo'); + } + + /** + * Display a listing of the resource. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function index(Request $request) + { + $user = $request->user(); + $data = [ + 'user' => $user, + 'can_change_password' => LegacyAuth::get()->canUpdatePasswords($user->username), + 'dashboards' => Dashboard::allAvailable($user)->with('user')->get(), + 'default_dashboard' => UserPref::getPref($user, 'dashboard'), + 'note_to_device' => UserPref::getPref($user, 'add_schedule_note_to_device'), + 'locale' => UserPref::getPref($user, 'locale') ?: 'en', + 'locales' => [ + 'en' => 'English', + 'ru' => 'русский', + ], + ]; + + if (Config::get('twofactor')) { + $twofactor = UserPref::getPref($user, 'twofactor'); + if ($twofactor) { + $data['twofactor_uri'] = TwoFactor::generateUri($user->username, $twofactor['key'], $twofactor['counter'] !== false); + } + $data['twofactor'] = $twofactor; + } + + if (!$user->hasGlobalRead()) { + $data['devices'] = Device::hasAccess($user)->get(); + } + + return view('user.preferences', $data); + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function store(Request $request) + { + $this->validate($request, [ + 'pref' => ['required', Rule::in(array_keys($this->valid_prefs))], + 'value' => $this->valid_prefs[$request->pref] ?? 'required|integer', + ]); + + UserPref::setPref($request->user(), $request->pref, $request->value); + + if ($request->pref == 'locale') { + Session::put('locale', $request->value); + } + + return response()->json(['status' => 'success']); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 3f7ed2d4c8..065326e2cc 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -34,6 +34,7 @@ class Kernel extends HttpKernel \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, // \Illuminate\Session\Middleware\AuthenticateSession::class, + \App\Http\Middleware\SetLocale::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \App\Http\Middleware\LegacyExternalAuth::class, @@ -66,6 +67,7 @@ class Kernel extends HttpKernel 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'deny-demo' => \App\Http\Middleware\DenyDemoUser::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, diff --git a/app/Http/Middleware/DenyDemoUser.php b/app/Http/Middleware/DenyDemoUser.php new file mode 100644 index 0000000000..79752a15b1 --- /dev/null +++ b/app/Http/Middleware/DenyDemoUser.php @@ -0,0 +1,24 @@ +user()->isDemo()) { + return response()->view('auth.deny-demo'); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/SetLocale.php b/app/Http/Middleware/SetLocale.php new file mode 100644 index 0000000000..a0c6b73278 --- /dev/null +++ b/app/Http/Middleware/SetLocale.php @@ -0,0 +1,34 @@ +user())) { + $locale = UserPref::getPref($request->user(), 'locale'); + Session::put('locale', $locale); + } + + if (!empty($locale)) { + App::setLocale($locale); + } + + return $next($request); + } +} diff --git a/app/Http/Requests/UpdateUserRequest.php b/app/Http/Requests/UpdateUserRequest.php index 8a557f3750..01c9c77fd2 100644 --- a/app/Http/Requests/UpdateUserRequest.php +++ b/app/Http/Requests/UpdateUserRequest.php @@ -43,6 +43,7 @@ class UpdateUserRequest extends FormRequest 'level' => 'int', 'old_password' => 'nullable|string', 'new_password' => 'nullable|confirmed|min:' . Config::get('password.min_length', 8), + 'new_password_confirmation' => 'nullable|same:new_password', 'dashboard' => 'int', ]; } diff --git a/config/app.php b/config/app.php index 1e0b7f2163..4dbff8d6f7 100644 --- a/config/app.php +++ b/config/app.php @@ -88,7 +88,7 @@ return [ | */ - 'locale' => 'en', + 'locale' => env('APP_LOCALE', 'en'), /* |-------------------------------------------------------------------------- diff --git a/includes/html/pages/preferences.inc.php b/includes/html/pages/preferences.inc.php deleted file mode 100644 index 642d4d2492..0000000000 --- a/includes/html/pages/preferences.inc.php +++ /dev/null @@ -1,223 +0,0 @@ -User Preferences'; -echo '
'; - -if (LegacyAuth::user()->isDemoUser()) { - demo_account(); -} else { - if ($_POST['action'] == 'changepass') { - if (LegacyAuth::get()->authenticate(['username' => LegacyAuth::user()->username, 'password' => $_POST['old_pass']])) { - if ($_POST['new_pass'] == '' || $_POST['new_pass2'] == '') { - $changepass_message = 'Password must not be blank.'; - } elseif ($_POST['new_pass'] == $_POST['new_pass2']) { - LegacyAuth::get()->changePassword(LegacyAuth::user()->username, $_POST['new_pass']); - $changepass_message = 'Password Changed.'; - } else { - $changepass_message = "Passwords don't match."; - } - } else { - $changepass_message = 'Incorrect password'; - } - } - if ($vars['action'] === 'changedash') { - if (!empty($vars['dashboard'])) { - set_user_pref('dashboard', (int)$vars['dashboard']); - $updatedashboard_message = "User default dashboard updated"; - } - } - if ($vars['action'] === 'changenote') { - set_user_pref('add_schedule_note_to_device', (bool)$vars['notetodevice']); - if ($vars['notetodevice']) { - $updatenote_message = "Schedule notes will now be added to device notes"; - } else { - $updatenote_message = "Schedule notes will no longer be added to device notes"; - } - } - - include 'includes/html/update-preferences-password.inc.php'; - - if (LegacyAuth::get()->canUpdatePasswords(LegacyAuth::user()->username)) { - echo '

Change Password

'; - echo '
'; - echo "
"; - echo $changepass_message; - echo "
- -
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
-
- -
"; - echo '
'; - }//end if - - if ($config['twofactor'] === true) { - $twofactor = get_user_pref('twofactor'); - echo ''; - echo '

Two-Factor Authentication

'; - echo '
'; - echo '
'; - if (!empty($twofactor)) { - $twofactor['text'] = "
- -
- -
-
"; - if ($twofactor['counter'] !== false) { - $twofactor['uri'] = 'otpauth://hotp/'.LegacyAuth::user()->username.'?issuer=LibreNMS&counter='.$twofactor['counter'].'&secret='.$twofactor['key']; - $twofactor['text'] .= "
- -
- -
-
"; - } else { - $twofactor['uri'] = 'otpauth://totp/'.LegacyAuth::user()->username.'?issuer=LibreNMS&secret='.$twofactor['key']; - } - - echo '
-
- -
'; - echo '
-
'.$twofactor['text'].'
- -
'; - echo ''; - echo '
- -
'; - } else { - echo '
-
- -
- -
-
-
-
- -
-
-
'; - }//end if - echo '
'; - }//end if -}//end if - -echo "

Default Dashboard

-
-
"; -if (!empty($updatedashboard_message)) { - print_message($updatedashboard_message); -} -echo " -
-
- -
- -
- -
-
-
-
-
-
-
-
'; - - -echo "

Add schedule notes to devices notes

-
-
"; -if (!empty($updatenote_message)) { - print_message($updatenote_message); -} -echo " -
-
- -
- -
- -
-
-
-
- -
-
-
-
-
-
"; - - - -echo "

Device Permissions

"; -echo "
"; -echo '
'; -if (LegacyAuth::user()->hasGlobalAdmin()) { - echo "Global Administrative Access"; -} elseif (LegacyAuth::user()->hasGlobalRead()) { - echo "Global Viewing Access"; -} else { - foreach (dbFetchRows('SELECT * FROM `devices_perms` AS P, `devices` AS D WHERE `user_id` = ? AND P.device_id = D.device_id', array(LegacyAuth::id())) as $perm) { - // FIXME generatedevicelink? - echo "".$perm['hostname'].'
'; - $dev_access = 1; - } - - if (!$dev_access) { - echo 'No access!'; - } -} - -echo '
'; - -echo ""; diff --git a/resources/views/auth/deny-demo.blade.php b/resources/views/auth/deny-demo.blade.php new file mode 100644 index 0000000000..36d189f17e --- /dev/null +++ b/resources/views/auth/deny-demo.blade.php @@ -0,0 +1,11 @@ +@extends('layouts.librenmsv1') + +@section('content') +
+
+
+ @lang('You are logged in as a demo account, this page is not accessible to you') +
+
+
+@endsection diff --git a/resources/views/layouts/librenmsv1.blade.php b/resources/views/layouts/librenmsv1.blade.php index 166b23a639..e935789a5e 100644 --- a/resources/views/layouts/librenmsv1.blade.php +++ b/resources/views/layouts/librenmsv1.blade.php @@ -108,6 +108,7 @@ @yield('content') +@yield('scripts') {!! Toastr::render() !!} diff --git a/resources/views/user/form.blade.php b/resources/views/user/form.blade.php index 5311383ea5..9ff193f0fe 100644 --- a/resources/views/user/form.blade.php +++ b/resources/views/user/form.blade.php @@ -72,6 +72,7 @@
diff --git a/resources/views/user/preferences.blade.php b/resources/views/user/preferences.blade.php new file mode 100644 index 0000000000..9586cf34db --- /dev/null +++ b/resources/views/user/preferences.blade.php @@ -0,0 +1,235 @@ +@extends('layouts.librenmsv1') + +@section('title', __('Preferences')) + +@section('content') +
+ + @lang('User Preferences') + + + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + + @if($can_change_password) +
+
@lang('Change Password')
+
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ @endif + +
+
@lang('Preferences')
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+ * @lang('Translation not fully supported') +
+
+
+ +
+ +
+
+
+
+
+ + @config('twofactor') +
+
@lang('Two-Factor Authentication')
+
+ @if($twofactor) +
+
+ + +
+
+
+
+ +
+ +
+
+ @if($twofactor['counter'] !== false) +
+ +
+ +
+
+ @endif +
+ +
+
+
+ +
+ @else +
+
+ +
+ +
+
+
+
+ +
+
+
+ @endif +
+
+ @endconfig + +
+
@lang('Device Permissions')
+
+ @if(auth()->user()->hasGlobalAdmin()) + @lang('Global Administrative Access') + @elseif(auth()->user()->hasGlobalRead()) + @lang('Global Viewing Access') + @else + @forelse($devices as $device) + {!! \LibreNMS\Util\Url::deviceLink($device) !!}
+ @empty + @lang('No access!') + @endforelse + @endif +
+
+
+@endsection + +@section('javascript') + + @endsection + +@section('scripts') + +@endsection + +@section('css') + +@endsection diff --git a/routes/web.php b/routes/web.php index 55312d77de..2113170d42 100644 --- a/routes/web.php +++ b/routes/web.php @@ -23,6 +23,8 @@ Route::group(['middleware' => ['auth', '2fa'], 'guard' => 'auth'], function () { // pages Route::get('locations', 'LocationController@index'); + Route::resource('preferences', 'UserPreferencesController', ['only' => ['index', 'store']]); + Route::resource('users', 'UserController'); // old route redirects Route::permanentRedirect('poll-log', 'pollers/tab=log/'); @@ -31,7 +33,7 @@ Route::group(['middleware' => ['auth', '2fa'], 'guard' => 'auth'], function () { 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('add', 'TwoFactorController@create')->name('2fa.add'); Route::post('cancel', 'TwoFactorController@cancelAdd')->name('2fa.cancel'); Route::post('remove', 'TwoFactorController@destroy')->name('2fa.remove'); @@ -39,8 +41,6 @@ Route::group(['middleware' => ['auth', '2fa'], 'guard' => 'auth'], function () { Route::delete('{user}', 'TwoFactorManagementController@destroy')->name('2fa.delete'); }); - Route::resource('users', 'UserController'); - // Ajax routes Route::group(['prefix' => 'ajax'], function () { // page ajax controllers