Basic check for user's administration ID

This commit is contained in:
James Cole
2023-01-29 07:00:26 +01:00
parent a5328a9ff4
commit e284da368d
7 changed files with 248 additions and 75 deletions

View File

@@ -25,11 +25,14 @@ namespace FireflyIII\Http\Requests;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\Location; use FireflyIII\Models\Location;
use FireflyIII\Models\UserRole;
use FireflyIII\Rules\UniqueIban; use FireflyIII\Rules\UniqueIban;
use FireflyIII\Support\Request\AppendsLocationData; use FireflyIII\Support\Request\AppendsLocationData;
use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Support\Request\ConvertsDataTypes;
use FireflyIII\Validation\Administration\ValidatesAdministrationAccess;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Validator;
/** /**
* Class AccountFormRequest. * Class AccountFormRequest.
@@ -39,6 +42,7 @@ class AccountFormRequest extends FormRequest
use ConvertsDataTypes; use ConvertsDataTypes;
use AppendsLocationData; use AppendsLocationData;
use ChecksLogin; use ChecksLogin;
use ValidatesAdministrationAccess;
/** /**
* Get all data. * Get all data.
@@ -48,6 +52,7 @@ class AccountFormRequest extends FormRequest
public function getAccountData(): array public function getAccountData(): array
{ {
$data = [ $data = [
'administration_id' => $this->convertInteger('administration_id'),
'name' => $this->convertString('name'), 'name' => $this->convertString('name'),
'active' => $this->boolean('active'), 'active' => $this->boolean('active'),
'account_type_name' => $this->convertString('objectType'), 'account_type_name' => $this->convertString('objectType'),
@@ -67,6 +72,9 @@ class AccountFormRequest extends FormRequest
'include_net_worth' => '1', 'include_net_worth' => '1',
'liability_direction' => $this->convertString('liability_direction'), 'liability_direction' => $this->convertString('liability_direction'),
]; ];
if (0 === $data['administration_id']) {
$data['administration_id'] = auth()->user()->getAdministrationId();
}
$data = $this->appendLocationData($data, 'location'); $data = $this->appendLocationData($data, 'location');
if (false === $this->boolean('include_net_worth')) { if (false === $this->boolean('include_net_worth')) {
@@ -101,6 +109,7 @@ class AccountFormRequest extends FormRequest
$types = implode(',', array_keys(config('firefly.subTitlesByIdentifier'))); $types = implode(',', array_keys(config('firefly.subTitlesByIdentifier')));
$ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes')));
$rules = [ $rules = [
'administration_id' => 'min:1|max:16777216|numeric',
'name' => 'required|min:1|uniqueAccountForUser', 'name' => 'required|min:1|uniqueAccountForUser',
'opening_balance' => 'numeric|nullable|max:1000000000', 'opening_balance' => 'numeric|nullable|max:1000000000',
'opening_balance_date' => 'date|required_with:opening_balance|nullable', 'opening_balance_date' => 'date|required_with:opening_balance|nullable',
@@ -130,4 +139,20 @@ class AccountFormRequest extends FormRequest
return $rules; return $rules;
} }
/**
* Configure the validator instance with special rules for after the basic validation rules.
*
* @param Validator $validator
*
* @return void
*/
public function withValidator(Validator $validator): void
{
$validator->after(
function (Validator $validator) {
// validate if the account can access this administration
$this->validateAdministration($validator, [UserRole::CHANGE_TRANSACTIONS]);
}
);
}
} }

View File

@@ -27,6 +27,7 @@ use Carbon\Carbon;
use Exception; use Exception;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\GroupMembership;
use FireflyIII\Models\InvitedUser; use FireflyIII\Models\InvitedUser;
use FireflyIII\Models\Role; use FireflyIII\Models\Role;
use FireflyIII\Models\UserGroup; use FireflyIII\Models\UserGroup;
@@ -466,4 +467,25 @@ class UserRepository implements UserRepositoryInterface
$invitee = InvitedUser::where('invite_code', $code)->where('expires', '>', $now->format('Y-m-d H:i:s'))->where('redeemed', 0)->first(); $invitee = InvitedUser::where('invite_code', $code)->where('expires', '>', $now->format('Y-m-d H:i:s'))->where('redeemed', 0)->first();
return null !== $invitee; return null !== $invitee;
} }
/**
* @inheritDoc
* @throws FireflyException
*/
public function getRolesInGroup(User $user, int $groupId): array
{
/** @var UserGroup $group */
$group = UserGroup::find($groupId);
if (null === $group) {
throw new FireflyException(sprintf('Could not find group #%d', $groupId));
}
$memberships = $group->groupMemberships()->where('user_id', $user->id)->get();
$roles = [];
/** @var GroupMembership $membership */
foreach ($memberships as $membership) {
$role = $membership->userRole;
$roles[] = $role->title;
}
return $roles;
}
} }

View File

@@ -40,6 +40,13 @@ interface UserRepositoryInterface
*/ */
public function all(): Collection; public function all(): Collection;
/**
* @param User $user
* @param int $groupId
* @return array
*/
public function getRolesInGroup(User $user, int $groupId): array;
/** /**
* Gives a user a role. * Gives a user a role.
* *

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Transformers; namespace FireflyIII\Transformers;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
@@ -40,6 +41,7 @@ class UserTransformer extends AbstractTransformer
* @param User $user * @param User $user
* *
* @return array * @return array
* @throws FireflyException
*/ */
public function transform(User $user): array public function transform(User $user): array
{ {
@@ -47,6 +49,7 @@ class UserTransformer extends AbstractTransformer
return [ return [
'id' => (int)$user->id, 'id' => (int)$user->id,
'administration_id' => (string)$user->getAdministrationId(),
'created_at' => $user->created_at->toAtomString(), 'created_at' => $user->created_at->toAtomString(),
'updated_at' => $user->updated_at->toAtomString(), 'updated_at' => $user->updated_at->toAtomString(),
'email' => $user->email, 'email' => $user->email,

View File

@@ -27,6 +27,7 @@ namespace FireflyIII;
use Eloquent; use Eloquent;
use Exception; use Exception;
use FireflyIII\Events\RequestedNewPassword; use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\Attachment; use FireflyIII\Models\Attachment;
use FireflyIII\Models\AvailableBudget; use FireflyIII\Models\AvailableBudget;
@@ -361,6 +362,20 @@ class User extends Authenticatable
return $this->hasMany(GroupMembership::class)->with(['userGroup', 'userRole']); return $this->hasMany(GroupMembership::class)->with(['userGroup', 'userRole']);
} }
/**
* A safe method that returns the user's current administration ID (group ID).
*
* @return int
* @throws FireflyException
*/
public function getAdministrationId(): int {
$groupId = (int)$this->user_group_id;
if(0 === $groupId) {
throw new FireflyException('User has no administration ID.');
}
return $groupId;
}
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* Link to object groups. * Link to object groups.

View File

@@ -0,0 +1,98 @@
<?php
/*
* ValidatesAdministrationAccess.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Validation\Administration;
use FireflyIII\Models\UserRole;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Validator;
/**
* Trait ValidatesAdministrationAccess
*/
trait ValidatesAdministrationAccess
{
/**
* @param Validator $validator
* @param array $allowedRoles
* @return void
* @throws AuthenticationException
*/
protected function validateAdministration(Validator $validator, array $allowedRoles): void
{
Log::debug('Now in validateAdministration()');
if (!auth()->check()) {
Log::error('User is not authenticated.');
throw new AuthenticationException('No access to validateAdministration() method.');
}
/** @var User $user */
$user = auth()->user();
// get data from request:
$data = $validator->getData();
// check if user is part of this administration
$administrationId = (int)($data['administration_id'] ?? $user->getAdministrationId());
// safety catch:
if (0 === $administrationId) {
Log::error('validateAdministration ran into empty administration ID.');
throw new AuthenticationException('Cannot validate administration.');
}
// grab the group:
$repository = app(UserRepositoryInterface::class);
// collect the user's roles in this group:
$administrationId = 2;
$array = $repository->getRolesInGroup($user, $administrationId);
if (0 === count($array)) {
Log::error(sprintf('User #%d ("%s") has no membership in group #%d.', $user->id, $user->email, $administrationId));
$validator->errors()->add('administration', (string)trans('validation.no_access_user_group'));
return;
}
if (in_array(UserRole::OWNER, $array, true)) {
Log::debug('User is owner of this administration.');
return;
}
if (in_array(UserRole::FULL, $array, true)) {
Log::debug('User has full access to this administration.');
return;
}
$access = true;
foreach ($allowedRoles as $allowedRole) {
if (!in_array($allowedRole, $array, true)) {
$access = false;
}
}
if (false === $access) {
Log::error(
sprintf(
'User #%d has memberships [%s] to group #%d but needs [%s].',
$user->id,
join(', ', $array),
$administrationId,
join(', ', $allowedRoles)
)
);
$validator->errors()->add('administration', (string)trans('validation.no_access_user_group'));
}
}
}

View File

@@ -240,6 +240,9 @@ return [
'amount_required_for_auto_budget' => 'The amount is required.', 'amount_required_for_auto_budget' => 'The amount is required.',
'auto_budget_amount_positive' => 'The amount must be more than zero.', 'auto_budget_amount_positive' => 'The amount must be more than zero.',
'auto_budget_period_mandatory' => 'The auto budget period is a mandatory field.', 'auto_budget_period_mandatory' => 'The auto budget period is a mandatory field.',
// no access to administration:
'no_access_user_group' => 'You do not have the correct access rights for this administration.',
]; ];
// Ignore this comment // Ignore this comment