Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] allow super admins to reset 2fa for other stuck users #1419

Merged
merged 9 commits into from
May 23, 2022
3 changes: 3 additions & 0 deletions lang/en/lang.php
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,9 @@
'2fa' => '2-factor authentication',
'2fa-description' => 'Please scan this QR code with a Google Authenticator compatible application and enter your one time password below before submitting. See a list of compatible applications <a href=":link" target="_blank" rel="noopener">here</a>.',
'2fa-disable' => 'Enter your one time password to disable the 2-factor authentication',
'force-2fa-disable' => 'Disable 2FA',
'force-2fa-disable-description' => 'Type text shown in the field to disable 2FA for this user',
'force-2fa-disable-challenge' => 'Disable 2FA for :user',
'active' => 'Active',
'pending' => 'Pending',
'activation-pending' => 'Pending activation',
Expand Down
49 changes: 38 additions & 11 deletions src/Http/Requests/Admin/UserRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,39 +29,66 @@ public function rules()
{
switch ($this->method()) {
case 'POST':
{
return [
{
return [
'name' => 'required',
'email' => 'required|email|unique:' . config('twill.users_table', 'twill_users') . ',email',
] + $this->getRoleValidator(['required']);
}
}
case 'PUT':
{
return [
{
return [
'name' => 'required',
'email' => 'required|email|unique:' . config('twill.users_table', 'twill_users') . ',email,' . $this->route('user'),
'email' => 'required|email|unique:' . config(
'twill.users_table',
'twill_users'
) . ',email,' . $this->route('user'),
'verify-code' => function ($attribute, $value, $fail) {
$user = Auth::guard('twill_users')->user();
$with2faSettings = config('twill.enabled.users-2fa') && $user->id == $this->route('user');

if ($with2faSettings) {
$userIsEnabling = $this->get('google_2fa_enabled') && ! $user->google_2fa_enabled;
$userIsDisabling = ! $this->get('google_2fa_enabled') && $user->google_2fa_enabled;
$userIsEnabling = $this->get('google_2fa_enabled') && !$user->google_2fa_enabled;
$userIsDisabling = !$this->get('google_2fa_enabled') && $user->google_2fa_enabled;

$shouldValidateOTP = $userIsEnabling || $userIsDisabling;

if ($shouldValidateOTP) {
$valid = (new Google2FA())->verifyKey($user->google_2fa_secret, $value ?? '');

if (! $valid) {
if (!$valid) {
$fail('Your one time password is invalid.');
}
}
}
},
'force-2fa-disable-challenge' => function ($attribute, $value, $fail) {
$user = User::findOrFail($this->route('user'));
if ($this->get('google_2fa_enabled') || !$user->google_2fa_enabled) {
return;
}

$loggedInAdmin = Auth::guard('twill_users')->user();
if (!$loggedInAdmin->can('manage-users')) {
return $fail('Unprivileged action');
}

if (!$loggedInAdmin->google_2fa_enabled) {
return $fail('You must have 2FA enabled to do this action');
}

$challenge = twillTrans(
'twill::lang.user-management.force-2fa-disable-challenge',
['user' => $user->email]
);
if ($value !== $challenge) {
return $fail('Challenge mismatch');
}
},
] + $this->getRoleValidator();
}
default:break;
}
default:
break;
}

return [];
Expand Down
24 changes: 15 additions & 9 deletions src/Repositories/Behaviors/HandleUserPermissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ trait HandleUserPermissions
*/
public function getFormFieldsHandleUserPermissions($object, $fields)
{
if (! config('twill.enabled.permissions-management')) {
if (!config('twill.enabled.permissions-management')) {
return $fields;
}

Expand All @@ -42,7 +42,7 @@ public function getFormFieldsHandleUserPermissions($object, $fields)
*/
public function afterSaveHandleUserPermissions($object, $fields)
{
if (! config('twill.enabled.permissions-management')) {
if (!config('twill.enabled.permissions-management')) {
return;
}

Expand All @@ -51,10 +51,14 @@ public function afterSaveHandleUserPermissions($object, $fields)
$this->updateUserItemPermissions($object, $fields);
}

private function addOrRemoveUserToEveryoneGroup($user)
private function addOrRemoveUserToEveryoneGroup(User $user)
{
$everyoneGroup = twillModel('group')::getEveryoneGroup();

if ($user->isSuperAdmin()) {
return;
}

if ($user->role->in_everyone_group) {
$user->groups()->syncWithoutDetaching([$everyoneGroup->id]);
} else {
Expand Down Expand Up @@ -96,20 +100,19 @@ private function updateUserItemPermissions($user, $fields)
*/
protected function getUserPermissionsFields($user, $fields)
{
if (! config('twill.enabled.permissions-management')) {
if (!config('twill.enabled.permissions-management')) {
return $fields;
}

$itemScopes = Permission::available(Permission::SCOPE_ITEM);

// looking for group permissions that belongs to the user
foreach ($user->publishedGroups as $group) {

// get each permissions that belongs to a module from this group
foreach ($group->permissions()->moduleItem()->get() as $permission) {
$model = $permission->permissionable()->first();

if (! $model) {
if (!$model) {
continue;
}

Expand All @@ -131,11 +134,14 @@ protected function getUserPermissionsFields($user, $fields)
}

// looking for global permissions, if the user has the 'manage-modules' permission
$isManageAllModules = $user->isSuperAdmin() || ($user->role->permissions()->global()->where('name', 'manage-modules')->first() != null);
$isManageAllModules = $user->isSuperAdmin() || ($user->role->permissions()->global()->where(
'name',
'manage-modules'
)->first() != null);

// looking for role module permission
$globalPermissions = [];
if (! $isManageAllModules) {
if (!$isManageAllModules) {
foreach ($user->role->permissions()->module()->get() as $permission) {
if ($permission->permissionable_type) {
$permissionName = str_replace('-module', '-item', $permission->name);
Expand All @@ -152,7 +158,7 @@ protected function getUserPermissionsFields($user, $fields)

foreach ($moduleItems as $moduleItem) {
$index = $moduleName . '_' . $moduleItem->id . '_permission';
if (! isset($fields[$index])) {
if (!isset($fields[$index])) {
$fields[$index] = "{$permission}";
} else {
$current = array_search($fields[$index], $itemScopes);
Expand Down
6 changes: 6 additions & 0 deletions src/Repositories/UserRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ public function prepareFieldsBeforeSave($user, $fields)
$fields['google_2fa_secret'] = null;
}

if ($this->config->get('twill.enabled.users-2fa', false)
&& ($fields['force-2fa-disable-challenge'] ?? false)) {
$user->google_2fa_enabled = false;
$user->google_2fa_secret = null;
}

return parent::prepareFieldsBeforeSave($user, $fields);
}

Expand Down
2 changes: 1 addition & 1 deletion views/layouts/form.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
<section class="col col--primary" data-sticky-top="publisher">
@unless($disableContentFieldset)
<a17-fieldset title="{{ $contentFieldsetLabel ?? twillTrans('twill::lang.form.content') }}" id="content">
@if (!empty($renderFields))
@if (isset($renderFields) && $renderFields->isNotEmpty())
@foreach($renderFields as $field)
{!! $field->render() !!}
@endforeach
Expand Down
2 changes: 1 addition & 1 deletion views/partials/form/_browser.blade.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<a17-inputframe label="{{ $label }}" name="browsers.{{ $name }}" note="{{ $fieldNote }}">
<a17-browserfield
{{$formFieldName()}}
{!! $formFieldName() !!}
item-label="{{ $itemLabel }}"
:max="{{ $max }}"
:wide="{{ json_encode($wide) }}"
Expand Down
2 changes: 1 addition & 1 deletion views/partials/form/_checkbox.blade.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<a17-singlecheckbox
{{$formFieldName()}}
{!! $formFieldName() !!}
label="{{ $label ?? '' }}"
:initial-value="{{ $default ? 'true' : 'false' }}"
@if ($note) note='{{ $note }}' @endif
Expand Down
2 changes: 1 addition & 1 deletion views/partials/form/_checkboxes.blade.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<a17-multiselect
label="{{ $label }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
:options="{{ json_encode($options) }}"
:grid="false"
:columns="{{ $columns }}"
Expand Down
2 changes: 1 addition & 1 deletion views/partials/form/_color.blade.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<a17-colorfield
label="{{ $label }}"
default-value="{{ $default ?? '' }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
in-store="value"
></a17-colorfield>

Expand Down
2 changes: 1 addition & 1 deletion views/partials/form/_date_picker.blade.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<a17-datepicker
label="{{ $label }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
place-holder="{{ $placeholder ?? $label }}"
@if ($disabled) disabled @endif
@if ($withTime) enable-time @endif
Expand Down
2 changes: 1 addition & 1 deletion views/partials/form/_files.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
max: {{ $max }},
filesizeMax: {{ $filesizeMax }},
@if ($buttonOnTop) buttonOnTop: true, @endif
{{$formFieldName(true)}}
{!! $formFieldName(true) !!}
}"
></a17-locale>

Expand Down
2 changes: 1 addition & 1 deletion views/partials/form/_hidden.blade.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<a17-hiddenfield
{{$formFieldName()}}
{!! $formFieldName() !!}
@if ($value) :initial-value="'{{ $value }}'" @endif
:has-default-store="true"
in-store="value"
Expand Down
4 changes: 2 additions & 2 deletions views/partials/form/_input.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
type="a17-textfield"
:attributes="{
label: '{{ $label }}',
{{$formFieldName(true)}},
{!! $formFieldName(true) !!},
type: '{{ $type }}',
@if ($required) required: true, @endif
@if ($note) note: '{{ $note }}', @endif
Expand All @@ -29,7 +29,7 @@
@else
<a17-textfield
label="{{ $label }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
type="{{ $type }}"
@if ($required) :required="true" @endif
@if ($note) note="{{ $note }}" @endif
Expand Down
2 changes: 1 addition & 1 deletion views/partials/form/_map.blade.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<a17-locationfield
label="{{ $label }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
@if ($showMap) show-map @else :show-map="false" @endif
@if ($openMap) open-map @endif
@if ($inModal) :in-modal="true" @endif
Expand Down
4 changes: 2 additions & 2 deletions views/partials/form/_medias.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
@if (!$withCaption) withCaption: false, @endif
@if ($buttonOnTop) buttonOnTop: true, @endif
@if (!$activeCrop) activeCrop: false, @endif
{{$formFieldName(true)}}
{!! $formFieldName(true) !!}
}"
>
{{ $note }}
Expand All @@ -36,7 +36,7 @@
@else
<a17-inputframe label="{{ $label }}" name="medias.{{ $name }}" @if ($required) :required="true" @endif @if ($fieldNote) note="{{ $fieldNote }}" @endif>
@if($multiple) <a17-slideshow @else <a17-mediafield @endif
{{$formFieldName()}}
{!! $formFieldName() !!}
crop-context="{{ $name }}"
:width-min="{{ $widthMin }}"
:height-min="{{ $heightMin }}"
Expand Down
4 changes: 2 additions & 2 deletions views/partials/form/_multi_select.blade.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@if ($unpack)
<a17-multiselect
label="{{ $label }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
:options="{{ json_encode($options) }}"
:grid="true"
:columns="{{ $columns }}"
Expand All @@ -21,7 +21,7 @@
@else
<a17-vselect
label="{{ $label }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
:options="{{ json_encode($options) }}"
@if ($emptyText ?? false) empty-text="{{ $emptyText }}" @endif
@if ($placeholder ?? false) placeholder="{{ $placeholder }}" @endif
Expand Down
2 changes: 1 addition & 1 deletion views/partials/form/_radios.blade.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<a17-singleselect
label="{{ $label }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
:options="{{ json_encode($options) }}"
@if ($default) selected="{{ $default }}" @endif
:grid="false"
Expand Down
6 changes: 3 additions & 3 deletions views/partials/form/_select.blade.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@if ($unpack ?? false)
<a17-singleselect
label="{{ $label }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
:options='{{ json_encode($options) }}'
:columns="{{ $columns }}"
@if (isset($default)) selected="{{ $default }}" @endif
Expand All @@ -26,7 +26,7 @@
@elseif ($native ?? false)
<a17-select
label="{{ $label }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
:options='{{ json_encode($options) }}'
@if ($placeholder) placeholder="{{ $placeholder }}" @endif
@if (isset($default)) selected="{{ $default }}" @endif
Expand All @@ -50,7 +50,7 @@
@else
<a17-vselect
label="{{ $label }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
:options='{{ json_encode($options) }}'
@if ($emptyText ?? false) empty-text="{{ $emptyText }}" @endif
@if ($placeholder) placeholder="{{ $placeholder }}" @endif
Expand Down
2 changes: 1 addition & 1 deletion views/partials/form/_select_permissions.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class="multiselectorPermissions__item"
@endif
<label for="{{ $name }}">{{ $itemInSelectsTables[$labelKey ?? 'title'] }}</label>
<a17-singleselect
{{$formFieldName()}}
{!! $formFieldName() !!}
:options='{{ json_encode($fctUpdatePermissionOptions($options, $isUserForm ? $item : $itemInSelectsTables, $isUserForm ? $itemInSelectsTables : $item)) }}'
@if ($default) selected="{{ $default }}" @endif
:in-table="true"
Expand Down
8 changes: 4 additions & 4 deletions views/partials/form/_wysiwyg.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
type="a17-wysiwyg-tiptap"
:attributes="{
label: '{{ $label }}',
{{$formFieldName(true)}},
{!! $formFieldName(true) !!},
@if ($note) note: '{{ $note }}', @endif
@if ($required) required: true, @endif
@if ($options) options: {!! e(json_encode($options)) !!}, @endif
Expand All @@ -32,7 +32,7 @@
@else
<a17-wysiwyg-tiptap
label="{{ $label }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
@if ($note) note="{{ $note }}" @endif
@if ($required) :required="true" @endif
@if ($options) :options='{!! json_encode($options) !!}' @endif
Expand All @@ -57,7 +57,7 @@
type="a17-wysiwyg"
:attributes="{
label: '{{ $label }}',
{{$formFieldName(true)}},
{!! $formFieldName(true) !!},
@if ($note) note: '{{ $note }}', @endif
@if ($required) required: true, @endif
@if ($options) options: {!! e(json_encode($options)) !!}, @endif
Expand All @@ -79,7 +79,7 @@
@else
<a17-wysiwyg
label="{{ $label }}"
{{$formFieldName()}}
{!! $formFieldName() !!}
@if ($note) note="{{ $note }}" @endif
@if ($required) :required="true" @endif
@if ($options) :options='{!! json_encode($options) !!}' @endif
Expand Down
Loading