Fixes #6821 - fixed 2 fa active for users list (#6822)

* Fixed #6821 - confusing UI for 2FA when 2FA is universally enforced

I also updated the language in the user’s listing table to clarify what “activated” means

* Added login enabled info to user view

* Clarified comments

* Added info about 2FA on user profile

Because why not

* Added nowrap to table, and added 2FA reset for superadmins
This commit is contained in:
snipe 2019-03-18 11:59:02 -07:00 committed by GitHub
parent 7b33f95e83
commit 0e1289f12f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 136 additions and 20 deletions

View file

@ -52,6 +52,7 @@ class UsersController extends Controller
'users.phone', 'users.phone',
'users.state', 'users.state',
'users.two_factor_enrolled', 'users.two_factor_enrolled',
'users.two_factor_optin',
'users.updated_at', 'users.updated_at',
'users.username', 'users.username',
'users.zip', 'users.zip',

View file

@ -53,6 +53,8 @@ class UsersTransformer
'permissions' => $user->decodePermissions(), 'permissions' => $user->decodePermissions(),
'activated' => ($user->activated =='1') ? true : false, 'activated' => ($user->activated =='1') ? true : false,
'two_factor_activated' => ($user->two_factor_active()) ? true : false, 'two_factor_activated' => ($user->two_factor_active()) ? true : false,
'two_factor_enrolled' => ($user->two_factor_active_and_enrolled()) ? true : false,
'assets_count' => (int) $user->assets_count, 'assets_count' => (int) $user->assets_count,
'licenses_count' => (int) $user->licenses_count, 'licenses_count' => (int) $user->licenses_count,
'accessories_count' => (int) $user->accessories_count, 'accessories_count' => (int) $user->accessories_count,

View file

@ -389,7 +389,11 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
} }
/** /**
* Check whether two-factor authorization is required and the user has activated it * Check whether two-factor authorization is requiredfor this user
*
* 0 = 2FA disabled
* 1 = 2FA optional
* 2 = 2FA universally required
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
@ -398,10 +402,45 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
*/ */
public function two_factor_active () { public function two_factor_active () {
if (Setting::getSettings()->two_factor_enabled !='0') { // If the 2FA is optional and the user has opted in
if (($this->two_factor_optin =='1') && ($this->two_factor_enrolled)) { if ((Setting::getSettings()->two_factor_enabled =='1') && ($this->two_factor_optin =='1'))
{
return true; return true;
} }
// If the 2FA is required for everyone so is implicitly active
elseif (Setting::getSettings()->two_factor_enabled =='2')
{
return true;
}
return false;
}
/**
* Check whether two-factor authorization is required and the user has activated it
* and enrolled a device
*
* 0 = 2FA disabled
* 1 = 2FA optional
* 2 = 2FA universally required
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.6.14]
*
* @return bool
*/
public function two_factor_active_and_enrolled () {
// If the 2FA is optional and the user has opted in and is enrolled
if ((Setting::getSettings()->two_factor_enabled =='1') && ($this->two_factor_optin =='1') && ($this->two_factor_enrolled =='1'))
{
return true;
}
// If the 2FA is required for everyone and the user has enrolled
elseif ((Setting::getSettings()->two_factor_enabled =='2') && ($this->two_factor_enrolled))
{
return true;
} }
return false; return false;

View file

@ -226,14 +226,14 @@ class UserPresenter extends Presenter
[ [
"field" => "two_factor_enrolled", "field" => "two_factor_enrolled",
"searchable" => false, "searchable" => false,
"sortable" => false, "sortable" => true,
"switchable" => true, "switchable" => true,
"title" => trans('admin/users/general.two_factor_enrolled'), "title" => trans('admin/users/general.two_factor_enrolled'),
"visible" => false, "visible" => false,
'formatter' => 'trueFalseFormatter' 'formatter' => 'trueFalseFormatter'
], ],
[ [
"field" => "two_factor_active", "field" => "two_factor_activated",
"searchable" => false, "searchable" => false,
"sortable" => false, "sortable" => false,
"switchable" => true, "switchable" => true,
@ -246,7 +246,7 @@ class UserPresenter extends Presenter
"searchable" => false, "searchable" => false,
"sortable" => true, "sortable" => true,
"switchable" => true, "switchable" => true,
"title" => trans('general.activated'), "title" => trans('general.login_enabled'),
"visible" => true, "visible" => true,
'formatter' => 'trueFalseFormatter' 'formatter' => 'trueFalseFormatter'
], ],

View file

@ -221,4 +221,5 @@
'zip' => 'Zip', 'zip' => 'Zip',
'noimage' => 'No image uploaded or image not found.', 'noimage' => 'No image uploaded or image not found.',
'token_expired' => 'Your form session has expired. Please try again.', 'token_expired' => 'Your form session has expired. Please try again.',
'login_enabled' => 'Login Enabled',
]; ];

View file

@ -127,22 +127,22 @@
<table class="table table-striped"> <table class="table table-striped">
@if (!is_null($user->company)) @if (!is_null($user->company))
<tr> <tr>
<td>{{ trans('general.company') }}</td> <td class="text-nowrap">{{ trans('general.company') }}</td>
<td>{{ $user->company->name }}</td> <td>{{ $user->company->name }}</td>
</tr> </tr>
@endif @endif
<tr> <tr>
<td>{{ trans('admin/users/table.name') }}</td> <td class="text-nowrap">{{ trans('admin/users/table.name') }}</td>
<td>{{ $user->present()->fullName() }}</td> <td>{{ $user->present()->fullName() }}</td>
</tr> </tr>
<tr> <tr>
<td>{{ trans('admin/users/table.username') }}</td> <td class="text-nowrap">{{ trans('admin/users/table.username') }}</td>
<td>{{ $user->username }}</td> <td>{{ $user->username }}</td>
</tr> </tr>
<tr> <tr>
<td>{{ trans('general.groups') }}</td> <td class="text-nowrap">{{ trans('general.groups') }}</td>
<td> <td>
@if ($user->groups->count() > 0) @if ($user->groups->count() > 0)
@foreach ($user->groups as $group) @foreach ($user->groups as $group)
@ -164,21 +164,21 @@
@if ($user->jobtitle) @if ($user->jobtitle)
<tr> <tr>
<td>{{ trans('admin/users/table.job') }}</td> <td class="text-nowrap">{{ trans('admin/users/table.job') }}</td>
<td>{{ $user->jobtitle }}</td> <td>{{ $user->jobtitle }}</td>
</tr> </tr>
@endif @endif
@if ($user->employee_num) @if ($user->employee_num)
<tr> <tr>
<td>{{ trans('admin/users/table.employee_num') }}</td> <td class="text-nowrap">{{ trans('admin/users/table.employee_num') }}</td>
<td>{{ $user->employee_num }}</td> <td>{{ $user->employee_num }}</td>
</tr> </tr>
@endif @endif
@if ($user->manager) @if ($user->manager)
<tr> <tr>
<td>{{ trans('admin/users/table.manager') }}</td> <td class="text-nowrap">{{ trans('admin/users/table.manager') }}</td>
<td> <td>
<a href="{{ route('users.show', $user->manager->id) }}">{{ $user->manager->getFullNameAttribute() }}</a> <a href="{{ route('users.show', $user->manager->id) }}">{{ $user->manager->getFullNameAttribute() }}</a>
@ -188,21 +188,21 @@
@if ($user->email) @if ($user->email)
<tr> <tr>
<td>{{ trans('admin/users/table.email') }}</td> <td class="text-nowrap">{{ trans('admin/users/table.email') }}</td>
<td><a href="mailto:{{ $user->email }}">{{ $user->email }}</a></td> <td><a href="mailto:{{ $user->email }}">{{ $user->email }}</a></td>
</tr> </tr>
@endif @endif
@if ($user->phone) @if ($user->phone)
<tr> <tr>
<td>{{ trans('admin/users/table.phone') }}</td> <td class="text-nowrap">{{ trans('admin/users/table.phone') }}</td>
<td><a href="tel:{{ $user->phone }}">{{ $user->phone }}</a></td> <td><a href="tel:{{ $user->phone }}">{{ $user->phone }}</a></td>
</tr> </tr>
@endif @endif
@if ($user->userloc) @if ($user->userloc)
<tr> <tr>
<td>{{ trans('admin/users/table.location') }}</td> <td class="text-nowrap">{{ trans('admin/users/table.location') }}</td>
<td>{{ link_to_route('locations.show', $user->userloc->name, [$user->userloc->id]) }}</td> <td>{{ link_to_route('locations.show', $user->userloc->name, [$user->userloc->id]) }}</td>
@ -210,14 +210,14 @@
@endif @endif
@if ($user->last_login) @if ($user->last_login)
<tr> <tr>
<td>{{ trans('general.last_login') }}</td> <td class="text-nowrap">{{ trans('general.last_login') }}</td>
<td>{{ \App\Helpers\Helper::getFormattedDateObject($user->last_login, 'datetime', false) }}</td> <td>{{ \App\Helpers\Helper::getFormattedDateObject($user->last_login, 'datetime', false) }}</td>
</tr> </tr>
@endif @endif
@if (!is_null($user->department)) @if (!is_null($user->department))
<tr> <tr>
<td>{{ trans('general.department') }}</td> <td class="text-nowrap">{{ trans('general.department') }}</td>
<td><a href="{{ route('departments.show', $user->department) }}">{{ $user->department->name }}</a></td> <td><a href="{{ route('departments.show', $user->department) }}">{{ $user->department->name }}</a></td>
</tr> </tr>
@endif @endif
@ -227,6 +227,45 @@
<td>{{ $user->created_at->format('F j, Y h:iA') }}</td> <td>{{ $user->created_at->format('F j, Y h:iA') }}</td>
</tr> </tr>
@endif @endif
<tr>
<td class="text-nowrap">{{ trans('general.login_enabled') }}</td>
<td>{{ ($user->activated=='1') ? trans('general.yes') : trans('general.no') }}</td>
</tr>
@if ($user->activated=='1')
<tr>
<td class="text-nowrap">{{ trans('admin/users/general.two_factor_active') }}</td>
<td>{{ ($user->two_factor_active()) ? trans('general.yes') : trans('general.no') }}</td>
</tr>
<tr>
<td class="text-nowrap">{{ trans('admin/users/general.two_factor_enrolled') }}</td>
<td class="two_factor_resetrow">
<div class="row">
<div class="col-md-1" id="two_factor_reset_toggle">
{{ ($user->two_factor_active_and_enrolled()) ? trans('general.yes') : trans('general.no') }}
</div>
@if ((Auth::user()->isSuperUser()) && ($snipeSettings->two_factor_enabled!='0'))
<div class="col-md-11">
<a class="btn btn-default btn-sm pull-left" id="two_factor_reset" style="margin-right: 10px;"> {{ trans('admin/settings/general.two_factor_reset') }}</a>
<span id="two_factor_reseticon">
</span>
<span id="two_factor_resetresult">
</span>
<span id="two_factor_resetstatus">
</span>
<br><br><p class="help-block">{{ trans('admin/settings/general.two_factor_reset_help') }}</p>
</div>
</div>
@endif
</td>
</tr>
@endif
</table> </table>
</div> </div>
</div> <!--/col-md-8--> </div> <!--/col-md-8-->
@ -542,6 +581,40 @@
@include ('partials.bootstrap-table', ['simple_view' => true]) @include ('partials.bootstrap-table', ['simple_view' => true])
<script nonce="{{ csrf_token() }}"> <script nonce="{{ csrf_token() }}">
$(function () { $(function () {
$("#two_factor_reset").click(function(){
$("#two_factor_resetrow").removeClass('success');
$("#two_factor_resetrow").removeClass('danger');
$("#two_factor_resetstatus").html('');
$("#two_factor_reseticon").html('<i class="fa fa-spinner spin"></i>');
$.ajax({
url: '{{ route('api.users.two_factor_reset', ['id'=> $user->id]) }}',
type: 'POST',
data: {},
headers: {
"X-Requested-With": 'XMLHttpRequest',
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content')
},
dataType: 'json',
success: function (data) {
$("#two_factor_reset_toggle").html('').html('{{ trans('general.no') }}');
$("#two_factor_reseticon").html('');
$("#two_factor_resetstatus").html('<i class="fa fa-check text-success"></i>' + data.message);
},
error: function (data) {
$("#two_factor_reseticon").html('');
$("#two_factor_reseticon").html('<i class="fa fa-exclamation-triangle text-danger"></i>');
$('#two_factor_resetstatus').text(data.message);
}
});
});
//binds to onchange event of your input field //binds to onchange event of your input field
var uploadedFileSize = 0; var uploadedFileSize = 0;
$('#fileupload').bind('change', function() { $('#fileupload').bind('change', function() {