mirror of
https://github.com/Bubka/2FAuth.git
synced 2024-11-22 00:03:09 +01:00
Add Group selector to the Advanced form - Closes #372
This commit is contained in:
parent
5d3a1be38f
commit
ba4e1edffa
@ -85,7 +85,7 @@ public function store(TwoFAccountDynamicRequest $request)
|
||||
$request->user()->twofaccounts()->save($twofaccount);
|
||||
|
||||
// Possible group association
|
||||
Groups::assign($twofaccount->id, $request->user());
|
||||
Groups::assign($twofaccount->id, $request->user(), Arr::get($validated, 'group_id', null));
|
||||
|
||||
return (new TwoFAccountReadResource($twofaccount->refresh()))
|
||||
->response()
|
||||
@ -106,6 +106,16 @@ public function update(TwoFAccountUpdateRequest $request, TwoFAccount $twofaccou
|
||||
$twofaccount->fillWithOtpParameters($validated);
|
||||
$request->user()->twofaccounts()->save($twofaccount);
|
||||
|
||||
// Possible group change
|
||||
$groupId = Arr::get($validated, 'group_id', null);
|
||||
if ($twofaccount->group_id != $groupId) {
|
||||
if ((int) $groupId === 0) {
|
||||
TwoFAccounts::withdraw($twofaccount->id);
|
||||
}
|
||||
else Groups::assign($twofaccount->id, $request->user(), $groupId);
|
||||
$twofaccount->refresh();
|
||||
}
|
||||
|
||||
return (new TwoFAccountReadResource($twofaccount))
|
||||
->response()
|
||||
->setStatusCode(200);
|
||||
|
@ -5,6 +5,8 @@
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Fluent;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class TwoFAccountDynamicRequest extends FormRequest
|
||||
{
|
||||
@ -32,6 +34,18 @@ public function rules()
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "withValidator" validation callables for the request.
|
||||
*/
|
||||
public function withValidator(Validator $validator) : void
|
||||
{
|
||||
// The account may have to be assign to a specific group.
|
||||
// If so, we check if the provided group exists.
|
||||
$validator->sometimes('group_id', 'exists:groups,id', function (Fluent $input) {
|
||||
return $input['group_id'] > 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
@ -45,5 +59,11 @@ protected function prepareForValidation()
|
||||
'otp_type' => strtolower($this->otp_type),
|
||||
'algorithm' => strtolower($this->algorithm),
|
||||
]);
|
||||
|
||||
if ($this->has('group_id') && $this->group_id === '') {
|
||||
$this->merge([
|
||||
'group_id' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ public function rules()
|
||||
'service' => 'nullable|string|regex:/^[^:]+$/i',
|
||||
'account' => 'required|string|regex:/^[^:]+$/i',
|
||||
'icon' => 'nullable|string',
|
||||
'group_id' => 'sometimes|nullable|integer|min:0',
|
||||
'otp_type' => 'required|string|in:totp,hotp,steamtotp',
|
||||
'secret' => ['string', 'bail', new \App\Rules\IsBase32Encoded],
|
||||
'digits' => 'nullable|integer|between:5,10',
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Fluent;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class TwoFAccountUpdateRequest extends FormRequest
|
||||
{
|
||||
@ -28,6 +30,7 @@ public function rules()
|
||||
'service' => 'present|nullable|string|regex:/^[^:]+$/i',
|
||||
'account' => 'required|string|regex:/^[^:]+$/i',
|
||||
'icon' => 'present|nullable|string',
|
||||
'group_id' => 'sometimes|nullable|integer|min:0',
|
||||
'otp_type' => 'required|string|in:totp,hotp,steamtotp',
|
||||
'secret' => ['present', 'string', 'bail', new \App\Rules\IsBase32Encoded],
|
||||
'digits' => 'present|integer|between:5,10',
|
||||
@ -37,6 +40,18 @@ public function rules()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "withValidator" validation callables for the request.
|
||||
*/
|
||||
public function withValidator(Validator $validator) : void
|
||||
{
|
||||
// The account may have to be assign to a specific group.
|
||||
// If so, we check if the provided group exists.
|
||||
$validator->sometimes('group_id', 'exists:groups,id', function (Fluent $input) {
|
||||
return $input['group_id'] > 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
|
@ -15,12 +15,39 @@ class GroupService
|
||||
* Assign one or more accounts to a group
|
||||
*
|
||||
* @param array|int $ids accounts ids to assign
|
||||
* @param \App\Models\Group|null $group The group the accounts will be assigned to
|
||||
* @param mixed $targetGroup The group the accounts should be assigned to
|
||||
*
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public static function assign($ids, User $user, ?Group $group = null) : void
|
||||
public static function assign($ids, User $user, mixed $targetGroup = null) : void
|
||||
{
|
||||
// targetGroup == 0 == The pseudo group named 'All' == No group
|
||||
// It means we do not want the accounts to be associated to a group, either a
|
||||
// specific group or the default group from user preferences.
|
||||
// If you need to release the accounts from an existing association, use the
|
||||
// TwoFAccountService::withdraw() method.
|
||||
if ($targetGroup === 0 || $targetGroup === '0') {
|
||||
Log::info('Group assignment skipped, no group explicitly requested');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Two main cases :
|
||||
// - A group (or group id) is passed as parameter => It has priority for use, if the group is valid
|
||||
// - No group is passed => We try to identify a destination group through user preferences
|
||||
$group = null;
|
||||
|
||||
if(! is_null($targetGroup)) {
|
||||
if ($targetGroup instanceof Group && $targetGroup->exists && $targetGroup->user_id == $user->id) {
|
||||
$group = $targetGroup;
|
||||
}
|
||||
else {
|
||||
$group = Group::where('id', (int) $targetGroup)
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
if (! $group) {
|
||||
$group = self::defaultGroup($user);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
import twofaccountService from '@/services/twofaccountService'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useTwofaccounts } from '@/stores/twofaccounts'
|
||||
import { useGroups } from '@/stores/groups'
|
||||
import { useBusStore } from '@/stores/bus'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
@ -22,6 +23,7 @@
|
||||
account: '',
|
||||
otp_type: '',
|
||||
icon: '',
|
||||
group_id: user.preferences.defaultGroup == -1 ? user.preferences.activeGroup : user.preferences.defaultGroup,
|
||||
secret: '',
|
||||
algorithm: '',
|
||||
digits: null,
|
||||
@ -85,10 +87,19 @@
|
||||
return props.twofaccountId != undefined
|
||||
})
|
||||
|
||||
const groups = computed(() => {
|
||||
return useGroups().items.map((item) => {
|
||||
return { text: item.id > 0 ? item.name : '- ' + trans('groups.no_group') + ' -', value: item.id }
|
||||
})
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (route.name == 'editAccount') {
|
||||
twofaccountService.get(props.twofaccountId).then(response => {
|
||||
form.fill(response.data)
|
||||
if (form.group_id == null) {
|
||||
form.group_id = 0
|
||||
}
|
||||
form.setOriginal()
|
||||
// set account icon as temp icon
|
||||
tempIcon.value = form.icon
|
||||
@ -513,6 +524,8 @@
|
||||
<FieldError v-if="iconForm.errors.hasAny('icon')" :error="iconForm.errors.get('icon')" :field="'icon'" class="help-for-file" />
|
||||
<p v-if="user.preferences.getOfficialIcons" class="help" v-html="$t('twofaccounts.forms.i_m_lucky_legend')"></p>
|
||||
</div>
|
||||
<!-- group -->
|
||||
<FormSelect v-if="groups.length > 0" v-model="form.group_id" :options="groups" fieldName="group_id" label="twofaccounts.forms.group.label" help="twofaccounts.forms.group.help" />
|
||||
<!-- otp type -->
|
||||
<FormToggle v-model="form.otp_type" :isDisabled="isEditMode" :choices="otp_types" fieldName="otp_type" :fieldError="form.errors.get('otp_type')" label="twofaccounts.forms.otp_type.label" help="twofaccounts.forms.otp_type.help" :hasOffset="true" />
|
||||
<div v-if="form.otp_type != ''">
|
||||
|
@ -60,6 +60,10 @@
|
||||
'i_m_lucky' => 'Try my luck',
|
||||
'i_m_lucky_legend' => 'The "Try my luck" button try to get the official icon of the given service. Enter actual service name without ".xyz" extension and try to avoid typo. (beta feature)',
|
||||
'test' => 'Test',
|
||||
'group' => [
|
||||
'label' => 'Group',
|
||||
'help' => 'The group to which the account is to be assigned'
|
||||
],
|
||||
'secret' => [
|
||||
'label' => 'Secret',
|
||||
'help' => 'The key used to generate your security codes'
|
||||
|
@ -517,6 +517,102 @@ public function test_store_with_invalid_uri_returns_validation_error()
|
||||
->assertStatus(422);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_store_assigns_created_account_to_provided_groupid()
|
||||
{
|
||||
// Set the default group to No group
|
||||
$this->user['preferences->defaultGroup'] = 0;
|
||||
$this->user->save();
|
||||
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
->json('POST', '/api/v1/twofaccounts', array_merge(
|
||||
OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP,
|
||||
['group_id' => $this->userGroupA->id]
|
||||
))
|
||||
->assertJsonFragment([
|
||||
'group_id' => $this->userGroupA->id,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_store_with_assignement_to_missing_groupid_returns_validation_error()
|
||||
{
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
->json('POST', '/api/v1/twofaccounts', array_merge(
|
||||
OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP,
|
||||
['group_id' => 9999999]
|
||||
))
|
||||
->assertJsonValidationErrorFor('group_id');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_store_with_assignement_to_null_groupid_does_not_assign_account_to_group()
|
||||
{
|
||||
// Set the default group to No group
|
||||
$this->user['preferences->defaultGroup'] = 0;
|
||||
$this->user->save();
|
||||
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
->json('POST', '/api/v1/twofaccounts', array_merge(
|
||||
OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP,
|
||||
['group_id' => null]
|
||||
))
|
||||
->assertJsonFragment([
|
||||
'group_id' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_store_with_assignement_to_null_groupid_is_overriden_by_specific_default_group()
|
||||
{
|
||||
// Set the default group to a specific group
|
||||
$this->user['preferences->defaultGroup'] = $this->userGroupA->id;
|
||||
$this->user->save();
|
||||
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
->json('POST', '/api/v1/twofaccounts', array_merge(
|
||||
OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP,
|
||||
['group_id' => null]
|
||||
))
|
||||
->assertJsonFragment([
|
||||
'group_id' => $this->user->preferences['defaultGroup'],
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_store_with_assignement_to_zero_groupid_overrides_specific_default_group()
|
||||
{
|
||||
// Set the default group to a specific group
|
||||
$this->user['preferences->defaultGroup'] = $this->userGroupA->id;
|
||||
$this->user->save();
|
||||
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
->json('POST', '/api/v1/twofaccounts', array_merge(
|
||||
OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP,
|
||||
['group_id' => 0]
|
||||
))
|
||||
->assertJsonFragment([
|
||||
'group_id' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_store_with_assignement_to_provided_groupid_overrides_specific_default_group()
|
||||
{
|
||||
// Set the default group to a specific group
|
||||
$this->user['preferences->defaultGroup'] = $this->userGroupA->id;
|
||||
$this->user->save();
|
||||
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
->json('POST', '/api/v1/twofaccounts', array_merge(
|
||||
OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP,
|
||||
['group_id' => $this->userGroupB->id]
|
||||
))
|
||||
->assertJsonFragment([
|
||||
'group_id' => $this->userGroupB->id,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_store_assigns_created_account_when_default_group_is_a_specific_one()
|
||||
{
|
||||
@ -529,7 +625,7 @@ public function test_store_assigns_created_account_when_default_group_is_a_speci
|
||||
'uri' => OtpTestData::TOTP_SHORT_URI,
|
||||
])
|
||||
->assertJsonFragment([
|
||||
'group_id' => $this->userGroupA->id,
|
||||
'group_id' => $this->user->preferences['defaultGroup'],
|
||||
]);
|
||||
}
|
||||
|
||||
@ -547,7 +643,7 @@ public function test_store_assigns_created_account_when_default_group_is_the_act
|
||||
'uri' => OtpTestData::TOTP_SHORT_URI,
|
||||
])
|
||||
->assertJsonFragment([
|
||||
'group_id' => $this->userGroupA->id,
|
||||
'group_id' => $this->user->preferences['activeGroup'],
|
||||
]);
|
||||
}
|
||||
|
||||
@ -609,6 +705,68 @@ public function test_update_missing_twofaccount_returns_not_found()
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_update_with_assignement_to_null_group_returns_success_with_updated_resource()
|
||||
{
|
||||
$this->assertNotEquals(null, $this->twofaccountA->group_id);
|
||||
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
->json('PUT', '/api/v1/twofaccounts/' . $this->twofaccountA->id, array_merge(
|
||||
OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP,
|
||||
['group_id' => null]
|
||||
))
|
||||
->assertOk()
|
||||
->assertJsonFragment([
|
||||
'group_id' => null
|
||||
])
|
||||
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_update_with_assignement_to_zero_group_returns_success_with_updated_resource()
|
||||
{
|
||||
$this->assertNotEquals(null, $this->twofaccountA->group_id);
|
||||
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
->json('PUT', '/api/v1/twofaccounts/' . $this->twofaccountA->id, array_merge(
|
||||
OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP,
|
||||
['group_id' => 0]
|
||||
))
|
||||
->assertOk()
|
||||
->assertJsonFragment([
|
||||
'group_id' => null
|
||||
])
|
||||
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_update_with_assignement_to_new_groupid_returns_success_with_updated_resource()
|
||||
{
|
||||
$this->assertEquals($this->userGroupA->id, $this->twofaccountA->group_id);
|
||||
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
->json('PUT', '/api/v1/twofaccounts/' . $this->twofaccountA->id, array_merge(
|
||||
OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP,
|
||||
['group_id' => $this->userGroupB->id]
|
||||
))
|
||||
->assertOk()
|
||||
->assertJsonFragment([
|
||||
'group_id' => $this->userGroupB->id
|
||||
])
|
||||
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_update_with_assignement_to_missing_groupid_returns_validation_error()
|
||||
{
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
->json('PUT', '/api/v1/twofaccounts/' . $this->twofaccountA->id, array_merge(
|
||||
OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP,
|
||||
['group_id' => 9999999]
|
||||
))
|
||||
->assertJsonValidationErrorFor('group_id');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_update_twofaccount_with_invalid_data_returns_validation_error()
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user