Permissions refactoring

* Migration: Added groups, privileges, user_groups, group_privileges, improved references
* Models: Added Group, Privilege and integrated it into User
* Replaced old permission handling with new models
This commit is contained in:
Igor Scheller 2022-11-06 12:41:52 +01:00 committed by GitHub
parent 35815b0838
commit 99afe3f651
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 923 additions and 331 deletions

View File

@ -0,0 +1,22 @@
<?php
namespace Database\Factories\Engelsystem\Models;
use Engelsystem\Models\Group;
use Illuminate\Database\Eloquent\Factories\Factory;
class GroupFactory extends Factory
{
/** @var string */
protected $model = Group::class;
/**
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->word(),
];
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Database\Factories\Engelsystem\Models;
use Engelsystem\Models\Privilege;
use Illuminate\Database\Eloquent\Factories\Factory;
class PrivilegeFactory extends Factory
{
/** @var string */
protected $model = Privilege::class;
/**
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->word(),
'description' => $this->faker->text(),
];
}
}

View File

@ -0,0 +1,274 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Schema\Blueprint;
use stdClass;
class CreatePrivilegesAndGroupsRelatedTables extends Migration
{
use ChangesReferences;
use Reference;
/**
* Creates the new table, copies the data and drops the old one.
*/
public function up(): void
{
$hasPrevious = $this->schema->hasTable('Privileges');
if ($hasPrevious) {
// Rename because some DBMS handle identifiers case-insensitive
$this->schema->rename('Groups', 'groups_old');
$this->schema->rename('Privileges', 'privileges_old');
}
$this->createNew();
if ($hasPrevious) {
$this->copyOldToNew();
$this->changeReferences(
'groups_old',
'UID',
'groups',
'id'
);
$this->changeReferences(
'privileges_old',
'id',
'privileges',
'id'
);
$this->changeReferences(
'UserGroups',
'id',
'users_groups',
'id'
);
$this->changeReferences(
'GroupPrivileges',
'id',
'group_privileges',
'id'
);
$this->schema->drop('UserGroups');
$this->schema->drop('GroupPrivileges');
$this->schema->drop('groups_old');
$this->schema->drop('privileges_old');
}
}
/**
* Recreates the previous table, copies the data and drops the new one.
*/
public function down(): void
{
// Rename because some DBMS handle identifiers case-insensitive
$this->schema->rename('groups', 'groups_new');
$this->schema->rename('privileges', 'privileges_new');
$this->createOldTable();
$this->copyNewToOld();
$this->changeReferences(
'groups_new',
'id',
'Groups',
'UID',
'integer'
);
$this->changeReferences(
'privileges_new',
'id',
'Privileges',
'id'
);
$this->changeReferences(
'users_groups',
'id',
'UserGroups',
'id'
);
$this->changeReferences(
'group_privileges',
'id',
'GroupPrivileges',
'id'
);
$this->schema->drop('users_groups');
$this->schema->drop('group_privileges');
$this->schema->drop('groups_new');
$this->schema->drop('privileges_new');
}
/**
* @return void
*/
protected function createNew(): void
{
$this->schema->create('groups', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 35)->unique();
});
$this->schema->create('privileges', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 128)->unique();
$table->string('description', 1024);
});
$this->schema->create('users_groups', function (Blueprint $table) {
$table->increments('id');
$this->referencesUser($table)->index();
$this->references($table, 'groups')->index();
});
$this->schema->create('group_privileges', function (Blueprint $table) {
$table->increments('id');
$this->references($table, 'groups')->index();
$this->references($table, 'privileges')->index();
});
}
/**
* @return void
*/
protected function createOldTable(): void
{
$this->schema->create('Groups', function (Blueprint $table) {
$table->string('Name', 35);
$table->integer('UID')->primary();
});
$this->schema->create('Privileges', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 128)->unique();
$table->string('desc', 1024);
});
$this->schema->create('UserGroups', function (Blueprint $table) {
$table->increments('id');
$this->references($table, 'users', 'uid');
$this->references($table, 'Groups', 'group_id', 'UID', false, 'integer')->index();
$table->index(['uid', 'group_id']);
});
$this->schema->create('GroupPrivileges', function (Blueprint $table) {
$table->increments('id');
$this->references($table, 'Groups', 'group_id', 'UID', false, 'integer');
$this->references($table, 'Privileges', 'privilege_id')->index();
$table->index(['group_id', 'privilege_id']);
});
}
/**
* @return void
*/
protected function copyOldToNew(): void
{
$connection = $this->schema->getConnection();
/** @var stdClass[] $records */
$records = $connection
->table('groups_old')
->get();
foreach ($records as $record) {
$connection->table('groups')->insert([
'id' => $record->UID,
'name' => $record->Name,
]);
}
$records = $connection
->table('privileges_old')
->get();
foreach ($records as $record) {
$connection->table('privileges')->insert([
'id' => $record->id,
'name' => $record->name,
'description' => $record->desc,
]);
}
$records = $connection
->table('UserGroups')
->get();
foreach ($records as $record) {
$connection->table('users_groups')->insert([
'id' => $record->id,
'user_id' => $record->uid,
'group_id' => $record->group_id,
]);
}
$records = $connection
->table('GroupPrivileges')
->get();
foreach ($records as $record) {
$connection->table('group_privileges')->insert([
'id' => $record->id,
'group_id' => $record->group_id,
'privilege_id' => $record->privilege_id,
]);
}
}
/**
* @return void
*/
protected function copyNewToOld(): void
{
$connection = $this->schema->getConnection();
/** @var Collection|stdClass[] $records */
$records = $connection
->table('groups_new')
->get();
foreach ($records as $record) {
$connection->table('Groups')->insert([
'Name' => $record->name,
'UID' => $record->id,
]);
}
$records = $connection
->table('privileges_new')
->get();
foreach ($records as $record) {
$connection->table('Privileges')->insert([
'id' => $record->id,
'name' => $record->name,
'desc' => $record->description,
]);
}
$records = $connection
->table('users_groups')
->get();
foreach ($records as $record) {
$connection->table('UserGroups')->insert([
'id' => $record->id,
'uid' => $record->user_id,
'group_id' => $record->group_id,
]);
}
$records = $connection
->table('group_privileges')
->get();
foreach ($records as $record) {
$connection->table('GroupPrivileges')->insert([
'id' => $record->id,
'group_id' => $record->group_id,
'privilege_id' => $record->privilege_id,
]);
}
}
}

View File

@ -11,47 +11,56 @@ trait Reference
/** /**
* @param Blueprint $table * @param Blueprint $table
* @param bool $setPrimary * @param bool $setPrimary
* @return ColumnDefinition
*/ */
protected function referencesUser(Blueprint $table, bool $setPrimary = false) protected function referencesUser(Blueprint $table, bool $setPrimary = false): ColumnDefinition
{ {
$this->references($table, 'users', null, $setPrimary); return $this->references($table, 'users', null, null, $setPrimary);
} }
/** /**
* @param Blueprint $table * @param Blueprint $table
* @param string $targetTable * @param string $targetTable
* @param string|null $fromColumn * @param string|null $fromColumn
* @param string|null $targetColumn
* @param bool $setPrimary * @param bool $setPrimary
* * @param string $type
* @return ColumnDefinition * @return ColumnDefinition
*/ */
protected function references( protected function references(
Blueprint $table, Blueprint $table,
string $targetTable, string $targetTable,
?string $fromColumn = null, ?string $fromColumn = null,
bool $setPrimary = false ?string $targetColumn = null,
bool $setPrimary = false,
string $type = 'unsignedInteger'
): ColumnDefinition { ): ColumnDefinition {
$fromColumn = $fromColumn ?? Str::singular($targetTable) . '_id'; $fromColumn = $fromColumn ?? Str::singular($targetTable) . '_id';
$col = $table->unsignedInteger($fromColumn); $col = $table->{$type}($fromColumn);
if ($setPrimary) { if ($setPrimary) {
$table->primary($fromColumn); $table->primary($fromColumn);
} }
$this->addReference($table, $fromColumn, $targetTable); $this->addReference($table, $fromColumn, $targetTable, $targetColumn ?: 'id');
return $col; return $col;
} }
/** /**
* @param Blueprint $table * @param Blueprint $table
* @param string $fromColumn * @param string $fromColumn
* @param string $targetTable * @param string $targetTable
* @param string|null $targetColumn
*/ */
protected function addReference(Blueprint $table, string $fromColumn, string $targetTable) protected function addReference(
{ Blueprint $table,
string $fromColumn,
string $targetTable,
?string $targetColumn = null
) {
$table->foreign($fromColumn) $table->foreign($fromColumn)
->references('id')->on($targetTable) ->references($targetColumn ?: 'id')->on($targetTable)
->onUpdate('cascade') ->onUpdate('cascade')
->onDelete('cascade'); ->onDelete('cascade');
} }

View File

@ -251,7 +251,7 @@ function user_controller()
auth()->can('admin_user'), auth()->can('admin_user'),
User_is_freeloader($user_source), User_is_freeloader($user_source),
User_angeltypes($user_source->id), User_angeltypes($user_source->id),
User_groups($user_source->id), $user_source->groups,
$shifts, $shifts,
$user->id == $user_source->id, $user->id == $user_source->id,
$tshirt_score, $tshirt_score,

View File

@ -5,7 +5,6 @@
*/ */
$includeFiles = [ $includeFiles = [
__DIR__ . '/../includes/sys_auth.php',
__DIR__ . '/../includes/sys_form.php', __DIR__ . '/../includes/sys_form.php',
__DIR__ . '/../includes/sys_log.php', __DIR__ . '/../includes/sys_log.php',
__DIR__ . '/../includes/sys_menu.php', __DIR__ . '/../includes/sys_menu.php',
@ -22,7 +21,6 @@ $includeFiles = [
__DIR__ . '/../includes/model/ShiftTypes_model.php', __DIR__ . '/../includes/model/ShiftTypes_model.php',
__DIR__ . '/../includes/model/Stats.php', __DIR__ . '/../includes/model/Stats.php',
__DIR__ . '/../includes/model/UserAngelTypes_model.php', __DIR__ . '/../includes/model/UserAngelTypes_model.php',
__DIR__ . '/../includes/model/UserGroups_model.php',
__DIR__ . '/../includes/model/User_model.php', __DIR__ . '/../includes/model/User_model.php',
__DIR__ . '/../includes/model/UserWorkLog_model.php', __DIR__ . '/../includes/model/UserWorkLog_model.php',
__DIR__ . '/../includes/model/ValidationResult.php', __DIR__ . '/../includes/model/ValidationResult.php',

View File

@ -78,7 +78,7 @@ function User_is_AngelType_supporter($user, $angeltype)
return false; return false;
} }
$privileges = privileges_for_user($user->id); $privileges = $user->privileges->pluck('name')->toArray();
return (count(Db::select( return (count(Db::select(
' '

View File

@ -1,23 +0,0 @@
<?php
use Engelsystem\Database\Db;
/**
* Returns users groups
*
* @param int $userId
* @return array[]
*/
function User_groups($userId)
{
return Db::select(
'
SELECT `Groups`.*
FROM `UserGroups`
JOIN `Groups` ON `Groups`.`UID`=`UserGroups`.`group_id`
WHERE `UserGroups`.`uid`=?
ORDER BY `UserGroups`.`group_id`
',
[$userId]
);
}

View File

@ -1,6 +1,9 @@
<?php <?php
use Engelsystem\Database\Db; use Engelsystem\Models\Group;
use Engelsystem\Models\Privilege;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
/** /**
* @return string * @return string
@ -17,18 +20,14 @@ function admin_groups()
{ {
$html = ''; $html = '';
$request = request(); $request = request();
$groups = Db::select('SELECT * FROM `Groups` ORDER BY `Name`'); /** @var Group[]|Collection $groups */
$groups = Group::query()->orderBy('name')->get();
if (!$request->has('action')) { if (!$request->has('action')) {
$groups_table = []; $groups_table = [];
foreach ($groups as $group) { foreach ($groups as $group) {
$privileges = Db::select(' /** @var Privilege[]|Collection $privileges */
SELECT `name` $privileges = $group->privileges()->orderBy('name')->get();
FROM `GroupPrivileges`
JOIN `Privileges` ON (`GroupPrivileges`.`privilege_id` = `Privileges`.`id`)
WHERE `group_id`=?
ORDER BY `name`
', [$group['UID']]);
$privileges_html = []; $privileges_html = [];
foreach ($privileges as $privilege) { foreach ($privileges as $privilege) {
@ -36,12 +35,12 @@ function admin_groups()
} }
$groups_table[] = [ $groups_table[] = [
'name' => $group['Name'], 'name' => $group->name,
'privileges' => join(', ', $privileges_html), 'privileges' => join(', ', $privileges_html),
'actions' => button( 'actions' => button(
page_link_to( page_link_to(
'admin_groups', 'admin_groups',
['action' => 'edit', 'id' => $group['UID']] ['action' => 'edit', 'id' => $group->id]
), ),
__('edit'), __('edit'),
'btn-sm' 'btn-sm'
@ -65,34 +64,26 @@ function admin_groups()
return error('Incomplete call, missing Groups ID.', true); return error('Incomplete call, missing Groups ID.', true);
} }
$group = Db::select('SELECT * FROM `Groups` WHERE `UID`=? LIMIT 1', [$group_id]); /** @var Group|null $group */
$group = Group::find($group_id);
if (!empty($group)) { if (!empty($group)) {
$privileges = Db::select(' $privileges = groupPrivilegesWithSelected($group);
SELECT `Privileges`.*, `GroupPrivileges`.`group_id`
FROM `Privileges`
LEFT OUTER JOIN `GroupPrivileges`
ON (
`Privileges`.`id` = `GroupPrivileges`.`privilege_id`
AND `GroupPrivileges`.`group_id`=?
)
ORDER BY `Privileges`.`name`
', [$group_id]);
$privileges_form = []; $privileges_form = [];
foreach ($privileges as $privilege) { foreach ($privileges as $privilege) {
$privileges_form[] = form_checkbox( $privileges_form[] = form_checkbox(
'privileges[]', 'privileges[]',
$privilege['desc'] . ' (' . $privilege['name'] . ')', $privilege->description . ' (' . $privilege->name . ')',
$privilege['group_id'] != '', $privilege->selected != '',
$privilege['id'], $privilege->id,
'privilege-' . $privilege['name'] 'privilege-' . $privilege->name
); );
} }
$privileges_form[] = form_submit('submit', __('Save')); $privileges_form[] = form_submit('submit', __('Save'));
$html .= page_with_title(__('Edit group'), [ $html .= page_with_title(__('Edit group') . ' ' . $group->name, [
form( form(
$privileges_form, $privileges_form,
page_link_to('admin_groups', ['action' => 'save', 'id' => $group_id]) page_link_to('admin_groups', ['action' => 'save', 'id' => $group->id])
) )
]); ]);
} else { } else {
@ -110,29 +101,21 @@ function admin_groups()
return error('Incomplete call, missing Groups ID.', true); return error('Incomplete call, missing Groups ID.', true);
} }
$group = Db::selectOne('SELECT * FROM `Groups` WHERE `UID`=? LIMIT 1', [$group_id]); /** @var Group|null $group */
$group = Group::find($group_id);
$privileges = $request->request->all('privileges'); $privileges = $request->request->all('privileges');
if (!empty($group)) { if (!empty($group)) {
Db::delete('DELETE FROM `GroupPrivileges` WHERE `group_id`=?', [$group_id]); $group->privileges()->detach();
$privilege_names = []; $privilege_names = [];
foreach ($privileges as $privilege) { foreach ($privileges as $privilege) {
$privilege = (int)$privilege; $privilege = Privilege::find($privilege);
if ($privilege) { if ($privilege) {
$group_privileges_source = Db::selectOne( $group->privileges()->attach($privilege);
'SELECT `name` FROM `Privileges` WHERE `id`=? LIMIT 1', $privilege_names[] = $privilege->name;
[$privilege]
);
if (!empty($group_privileges_source)) {
Db::insert(
'INSERT INTO `GroupPrivileges` (`group_id`, `privilege_id`) VALUES (?, ?)',
[$group_id, $privilege]
);
$privilege_names[] = $group_privileges_source['name'];
}
} }
} }
engelsystem_log( engelsystem_log(
'Group privileges of group ' . $group['Name'] 'Group privileges of group ' . $group->name
. ' edited: ' . join(', ', $privilege_names) . ' edited: ' . join(', ', $privilege_names)
); );
throw_redirect(page_link_to('admin_groups')); throw_redirect(page_link_to('admin_groups'));
@ -144,3 +127,24 @@ function admin_groups()
} }
return $html; return $html;
} }
/**
* @param Group $group
* @return Collection|Privilege[]
*/
function groupPrivilegesWithSelected(Group $group): Collection
{
return Privilege::query()
->join('group_privileges', function ($query) use ($group) {
/** @var JoinClause $query */
$query
->where('privileges.id', '=', $query->raw('group_privileges.privilege_id'))
->where('group_privileges.group_id', $group->id)
;
}, null, null, 'left outer')
->orderBy('name')
->get([
'privileges.*',
'group_privileges.group_id as selected'
]);
}

View File

@ -1,7 +1,9 @@
<?php <?php
use Engelsystem\Database\DB; use Engelsystem\Models\Group;
use Engelsystem\Models\User\User; use Engelsystem\Models\User\User;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
/** /**
* @return string * @return string
@ -126,20 +128,15 @@ function admin_user()
$html .= '<hr />'; $html .= '<hr />';
$my_highest_group = DB::selectOne( /** @var Group $my_highest_group */
'SELECT group_id FROM `UserGroups` WHERE `uid`=? ORDER BY `group_id` DESC LIMIT 1', $my_highest_group = $user->groups()->orderByDesc('id')->first();
[$user->id]
);
if (!empty($my_highest_group)) { if (!empty($my_highest_group)) {
$my_highest_group = $my_highest_group['group_id']; $my_highest_group = $my_highest_group->id;
} }
$angel_highest_group = DB::selectOne( $angel_highest_group = $user_source->groups()->orderByDesc('id')->first();
'SELECT group_id FROM `UserGroups` WHERE `uid`=? ORDER BY `group_id` DESC LIMIT 1',
[$user_id]
);
if (!empty($angel_highest_group)) { if (!empty($angel_highest_group)) {
$angel_highest_group = $angel_highest_group['group_id']; $angel_highest_group = $angel_highest_group->id;
} }
if ( if (
@ -152,26 +149,11 @@ function admin_user()
$html .= form_csrf(); $html .= form_csrf();
$html .= '<table>'; $html .= '<table>';
$groups = DB::select( $groups = changeableGroups($my_highest_group, $user_id);
'
SELECT *
FROM `Groups`
LEFT OUTER JOIN `UserGroups` ON (
`UserGroups`.`group_id` = `Groups`.`UID`
AND `UserGroups`.`uid` = ?
)
WHERE `Groups`.`UID` <= ?
ORDER BY `Groups`.`Name`
',
[
$user_id,
$my_highest_group,
]
);
foreach ($groups as $group) { foreach ($groups as $group) {
$html .= '<tr><td><input type="checkbox" name="groups[]" value="' . $group['UID'] . '" ' $html .= '<tr><td><input type="checkbox" name="groups[]" value="' . $group->id . '" '
. ($group['group_id'] != '' ? ' checked="checked"' : '') . ($group->selected ? ' checked="checked"' : '')
. ' /></td><td>' . $group['Name'] . '</td></tr>'; . ' /></td><td>' . $group->name . '</td></tr>';
} }
$html .= '</table><br>'; $html .= '</table><br>';
@ -190,44 +172,26 @@ function admin_user()
} else { } else {
switch ($request->input('action')) { switch ($request->input('action')) {
case 'save_groups': case 'save_groups':
if ($user_id != $user->id || auth()->can('admin_groups')) { $angel = User::findOrFail($user_id);
$my_highest_group = DB::selectOne( if ($angel->id != $user->id || auth()->can('admin_groups')) {
'SELECT * FROM `UserGroups` WHERE `uid`=? ORDER BY `group_id` DESC LIMIT 1', /** @var Group $my_highest_group */
[$user->id] $my_highest_group = $user->groups()->orderByDesc('id')->first();
); /** @var Group $angel_highest_group */
$angel_highest_group = DB::selectOne( $angel_highest_group = $angel->groups()->orderByDesc('id')->first();
'SELECT * FROM `UserGroups` WHERE `uid`=? ORDER BY `group_id` DESC LIMIT 1',
[$user_id]
);
if ( if (
$my_highest_group $my_highest_group
&& ( && (
empty($angel_highest_group) empty($angel_highest_group)
|| ($my_highest_group['group_id'] >= $angel_highest_group['group_id']) || ($my_highest_group->id >= $angel_highest_group->id)
) )
) { ) {
$groups_source = DB::select( $groups_source = changeableGroups($my_highest_group->id, $angel->id);
'
SELECT *
FROM `Groups`
LEFT OUTER JOIN `UserGroups` ON (
`UserGroups`.`group_id` = `Groups`.`UID`
AND `UserGroups`.`uid` = ?
)
WHERE `Groups`.`UID` <= ?
ORDER BY `Groups`.`Name`
',
[
$user_id,
$my_highest_group['group_id'],
]
);
$groups = []; $groups = [];
$grouplist = []; $groupList = [];
foreach ($groups_source as $group) { foreach ($groups_source as $group) {
$groups[$group['UID']] = $group; $groups[$group->id] = $group;
$grouplist[] = $group['UID']; $groupList[] = $group->id;
} }
$groupsRequest = $request->input('groups'); $groupsRequest = $request->input('groups');
@ -235,20 +199,17 @@ function admin_user()
$groupsRequest = []; $groupsRequest = [];
} }
DB::delete('DELETE FROM `UserGroups` WHERE `uid`=?', [$user_id]); $angel->groups()->detach();
$user_groups_info = []; $user_groups_info = [];
foreach ($groupsRequest as $group) { foreach ($groupsRequest as $group) {
if (in_array($group, $grouplist)) { if (in_array($group, $groupList)) {
DB::insert( $group = $groups[$group];
'INSERT INTO `UserGroups` (`uid`, `group_id`) VALUES (?, ?)', $angel->groups()->attach($group);
[$user_id, $group] $user_groups_info[] = $group->name;
);
$user_groups_info[] = $groups[$group]['Name'];
} }
} }
$user_source = User::find($user_id);
engelsystem_log( engelsystem_log(
'Set groups of ' . User_Nick_render($user_source, true) . ' to: ' 'Set groups of ' . User_Nick_render($angel, true) . ' to: '
. join(', ', $user_groups_info) . join(', ', $user_groups_info)
); );
$html .= success('Benutzergruppen gespeichert.', true); $html .= success('Benutzergruppen gespeichert.', true);
@ -321,3 +282,24 @@ function admin_user()
$html $html
]); ]);
} }
/**
* @param $myHighestGroup
* @param $angelId
* @return Collection|Group[]
*/
function changeableGroups($myHighestGroup, $angelId): Collection
{
return Group::query()
->where('groups.id', '<=', $myHighestGroup)
->join('users_groups', function ($query) use ($angelId) {
/** @var JoinClause $query */
$query->where('users_groups.group_id', '=', $query->raw('groups.id'))
->where('users_groups.user_id', $angelId);
}, null, null, 'left outer')
->orderBy('name')
->get([
'groups.*',
'users_groups.group_id as selected'
]);
}

View File

@ -4,6 +4,7 @@ use Carbon\Carbon;
use Engelsystem\Database\Database; use Engelsystem\Database\Database;
use Engelsystem\Database\Db; use Engelsystem\Database\Db;
use Engelsystem\Events\Listener\OAuth2; use Engelsystem\Events\Listener\OAuth2;
use Engelsystem\Models\Group;
use Engelsystem\Models\OAuth; use Engelsystem\Models\OAuth;
use Engelsystem\Models\User\Contact; use Engelsystem\Models\User\Contact;
use Engelsystem\Models\User\PersonalData; use Engelsystem\Models\User\PersonalData;
@ -299,7 +300,8 @@ function guest_register()
} }
// Assign user-group and set password // Assign user-group and set password
Db::insert('INSERT INTO `UserGroups` (`uid`, `group_id`) VALUES (?, 20)', [$user->id]); $defaultGroup = Group::find(auth()->getDefaultRole());
$user->groups()->attach($defaultGroup);
if ($enable_password) { if ($enable_password) {
auth()->setPassword($user, $request->postData('password')); auth()->setPassword($user, $request->postData('password'));
} }

View File

@ -1,43 +0,0 @@
<?php
use Engelsystem\Database\Db;
/**
* @param int $user_id
* @return array
*/
function privileges_for_user($user_id)
{
$privileges = [];
$user_privileges = Db::select('
SELECT `Privileges`.`name`
FROM `users`
JOIN `UserGroups` ON (`users`.`id` = `UserGroups`.`uid`)
JOIN `GroupPrivileges` ON (`UserGroups`.`group_id` = `GroupPrivileges`.`group_id`)
JOIN `Privileges` ON (`GroupPrivileges`.`privilege_id` = `Privileges`.`id`)
WHERE `users`.`id`=?
', [$user_id]);
foreach ($user_privileges as $user_privilege) {
$privileges[] = $user_privilege['name'];
}
return $privileges;
}
/**
* @param int $group_id
* @return array
*/
function privileges_for_group($group_id)
{
$privileges = [];
$groups_privileges = Db::select('
SELECT `name`
FROM `GroupPrivileges`
JOIN `Privileges` ON (`GroupPrivileges`.`privilege_id` = `Privileges`.`id`)
WHERE `group_id`=?
', [$group_id]);
foreach ($groups_privileges as $guest_privilege) {
$privileges[] = $guest_privilege['name'];
}
return $privileges;
}

View File

@ -1,6 +1,7 @@
<?php <?php
use Carbon\Carbon; use Carbon\Carbon;
use Engelsystem\Models\Group;
use Engelsystem\Models\Room; use Engelsystem\Models\Room;
use Engelsystem\Models\User\User; use Engelsystem\Models\User\User;
use Engelsystem\Models\Worklog; use Engelsystem\Models\Worklog;
@ -431,7 +432,7 @@ function User_view_worklog(Worklog $worklog, $admin_user_worklog_privilege)
* @param bool $admin_user_privilege * @param bool $admin_user_privilege
* @param bool $freeloader * @param bool $freeloader
* @param array[] $user_angeltypes * @param array[] $user_angeltypes
* @param array[] $user_groups * @param Group[] $user_groups
* @param array[] $shifts * @param array[] $shifts
* @param bool $its_me * @param bool $its_me
* @param int $tshirt_score * @param int $tshirt_score
@ -734,14 +735,14 @@ function User_angeltypes_render($user_angeltypes)
} }
/** /**
* @param array[] $user_groups * @param Group[] $user_groups
* @return string * @return string
*/ */
function User_groups_render($user_groups) function User_groups_render($user_groups)
{ {
$output = []; $output = [];
foreach ($user_groups as $group) { foreach ($user_groups as $group) {
$output[] = __($group['Name']); $output[] = __($group->name);
} }
return div('col-md-2', [ return div('col-md-2', [

View File

@ -3,6 +3,7 @@
namespace Engelsystem\Helpers; namespace Engelsystem\Helpers;
use Carbon\Carbon; use Carbon\Carbon;
use Engelsystem\Models\Group;
use Engelsystem\Models\User\User; use Engelsystem\Models\User\User;
use Engelsystem\Models\User\User as UserRepository; use Engelsystem\Models\User\User as UserRepository;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
@ -10,26 +11,29 @@ use Symfony\Component\HttpFoundation\Session\Session;
class Authenticator class Authenticator
{ {
/** @var User */ /** @var User|null */
protected $user = null; protected ?User $user = null;
/** @var ServerRequestInterface */ /** @var ServerRequestInterface */
protected $request; protected ServerRequestInterface $request;
/** @var Session */ /** @var Session */
protected $session; protected Session $session;
/** @var UserRepository */ /** @var UserRepository */
protected $userRepository; protected UserRepository $userRepository;
/** @var string[] */ /** @var string[] */
protected $permissions; protected array $permissions = [];
/** @var int|string|null */ /** @var int|string|null */
protected $passwordAlgorithm = PASSWORD_DEFAULT; protected $passwordAlgorithm = PASSWORD_DEFAULT;
/** @var int */ /** @var int */
protected $guestRole = 10; protected int $defaultRole = 20;
/** @var int */
protected int $guestRole = 10;
/** /**
* @param ServerRequestInterface $request * @param ServerRequestInterface $request
@ -48,7 +52,7 @@ class Authenticator
* *
* @return User|null * @return User|null
*/ */
public function user() public function user(): ?User
{ {
if ($this->user) { if ($this->user) {
return $this->user; return $this->user;
@ -77,7 +81,7 @@ class Authenticator
* @param string $parameter * @param string $parameter
* @return User|null * @return User|null
*/ */
public function apiUser($parameter = 'api_key') public function apiUser(string $parameter = 'api_key'): ?User
{ {
if ($this->user) { if ($this->user) {
return $this->user; return $this->user;
@ -88,6 +92,7 @@ class Authenticator
return null; return null;
} }
/** @var User|null $user */
$user = $this $user = $this
->userRepository ->userRepository
->whereApiKey($params[$parameter]) ->whereApiKey($params[$parameter])
@ -113,7 +118,7 @@ class Authenticator
$user = $this->user(); $user = $this->user();
if ($user) { if ($user) {
$this->permissions = $this->getPermissionsByUser($user); $this->permissions = $user->privileges->pluck('name')->toArray();
$user->last_login_at = new Carbon(); $user->last_login_at = new Carbon();
$user->save(); $user->save();
@ -122,7 +127,9 @@ class Authenticator
} }
if (empty($this->permissions)) { if (empty($this->permissions)) {
$this->permissions = $this->getPermissionsByGroup($this->guestRole); /** @var Group $group */
$group = Group::find($this->guestRole);
$this->permissions = $group->privileges->pluck('name')->toArray();
} }
} }
@ -140,7 +147,7 @@ class Authenticator
* @param string $password * @param string $password
* @return User|null * @return User|null
*/ */
public function authenticate(string $login, string $password) public function authenticate(string $login, string $password): ?User
{ {
/** @var User $user */ /** @var User $user */
$user = $this->userRepository->whereName($login)->first(); $user = $this->userRepository->whereName($login)->first();
@ -164,7 +171,7 @@ class Authenticator
* @param string $password * @param string $password
* @return bool * @return bool
*/ */
public function verifyPassword(User $user, string $password) public function verifyPassword(User $user, string $password): bool
{ {
if (!password_verify($password, $user->password)) { if (!password_verify($password, $user->password)) {
return false; return false;
@ -206,7 +213,23 @@ class Authenticator
/** /**
* @return int * @return int
*/ */
public function getGuestRole() public function getDefaultRole(): int
{
return $this->defaultRole;
}
/**
* @param int $defaultRole
*/
public function setDefaultRole(int $defaultRole)
{
$this->defaultRole = $defaultRole;
}
/**
* @return int
*/
public function getGuestRole(): int
{ {
return $this->guestRole; return $this->guestRole;
} }
@ -218,24 +241,4 @@ class Authenticator
{ {
$this->guestRole = $guestRole; $this->guestRole = $guestRole;
} }
/**
* @param User $user
* @return array
* @codeCoverageIgnore
*/
protected function getPermissionsByUser($user)
{
return privileges_for_user($user->id);
}
/**
* @param int $groupId
* @return array
* @codeCoverageIgnore
*/
protected function getPermissionsByGroup(int $groupId)
{
return privileges_for_group($groupId);
}
} }

View File

@ -15,6 +15,7 @@ class AuthenticatorServiceProvider extends ServiceProvider
$authenticator = $this->app->make(Authenticator::class); $authenticator = $this->app->make(Authenticator::class);
$authenticator->setPasswordAlgorithm($config->get('password_algorithm')); $authenticator->setPasswordAlgorithm($config->get('password_algorithm'));
$authenticator->setGuestRole($config->get('auth_guest_role', $authenticator->getGuestRole())); $authenticator->setGuestRole($config->get('auth_guest_role', $authenticator->getGuestRole()));
$authenticator->setDefaultRole($config->get('auth_default_role', $authenticator->getDefaultRole()));
$this->app->instance(Authenticator::class, $authenticator); $this->app->instance(Authenticator::class, $authenticator);
$this->app->instance('authenticator', $authenticator); $this->app->instance('authenticator', $authenticator);

47
src/Models/Group.php Normal file
View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Models;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* @property int $id
* @property string $name
*
* @property-read Collection|Privilege[] $privileges
* @property-read Collection|User[] $users
*
* @method static Builder|Group whereId($value)
* @method static Builder|Group whereName($value)
*/
class Group extends BaseModel
{
use HasFactory;
/** @var string[] */
protected $fillable = [
'name',
];
/**
* @return BelongsToMany
*/
public function privileges(): BelongsToMany
{
return $this->belongsToMany(Privilege::class, 'group_privileges');
}
/**
* @return BelongsToMany
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, 'users_groups');
}
}

40
src/Models/Privilege.php Normal file
View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* @property int $id
* @property string $name
* @property string $description
*
* @property-read Collection|Group[] $groups
*
* @method static Builder|Privilege whereId($value)
* @method static Builder|Privilege whereName($value)
* @method static Builder|Privilege whereDescription($value)
*/
class Privilege extends BaseModel
{
use HasFactory;
/** @var string[] */
protected $fillable = [
'name',
'description',
];
/**
* @return BelongsToMany
*/
public function groups(): BelongsToMany
{
return $this->belongsToMany(Group::class, 'group_privileges');
}
}

View File

@ -4,43 +4,51 @@ namespace Engelsystem\Models\User;
use Carbon\Carbon; use Carbon\Carbon;
use Engelsystem\Models\BaseModel; use Engelsystem\Models\BaseModel;
use Engelsystem\Models\Group;
use Engelsystem\Models\Message; use Engelsystem\Models\Message;
use Engelsystem\Models\News; use Engelsystem\Models\News;
use Engelsystem\Models\NewsComment; use Engelsystem\Models\NewsComment;
use Engelsystem\Models\OAuth; use Engelsystem\Models\OAuth;
use Engelsystem\Models\Privilege;
use Engelsystem\Models\Question; use Engelsystem\Models\Question;
use Engelsystem\Models\Worklog; use Engelsystem\Models\Worklog;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Support\Collection as SupportCollection;
/** /**
* @property int $id * @property int $id
* @property string $name * @property string $name
* @property string $email * @property string $email
* @property string $password * @property string $password
* @property string $api_key * @property string $api_key
* @property Carbon|null $last_login_at * @property Carbon|null $last_login_at
* @property Carbon $created_at * @property Carbon $created_at
* @property Carbon $updated_at * @property Carbon $updated_at
* *
* @property-read QueryBuilder|Contact $contact * @property-read QueryBuilder|Contact $contact
* @property-read QueryBuilder|License $license * @property-read QueryBuilder|License $license
* @property-read QueryBuilder|PersonalData $personalData * @property-read QueryBuilder|PersonalData $personalData
* @property-read QueryBuilder|Settings $settings * @property-read QueryBuilder|Settings $settings
* @property-read QueryBuilder|State $state * @property-read QueryBuilder|State $state
* @property-read Collection|News[] $news *
* @property-read Collection|NewsComment[] $newsComments * @property-read Collection|Group[] $groups
* @property-read Collection|OAuth[] $oauth * @property-read Collection|News[] $news
* @property-read Collection|Worklog[] $worklogs * @property-read Collection|NewsComment[] $newsComments
* @property-read Collection|Worklog[] $worklogsCreated * @property-read Collection|OAuth[] $oauth
* @property-read int|null $news_count * @property-read SupportCollection|Privilege[] $privileges
* @property-read int|null $news_comments_count * @property-read Collection|Worklog[] $worklogs
* @property-read int|null $oauth_count * @property-read Collection|Worklog[] $worklogsCreated
* @property-read int|null $worklogs_count * @property-read Collection|Question[] $questionsAsked
* @property-read int|null $worklogs_created_count * @property-read Collection|Question[] $questionsAnswered
* @property-read Collection|Message[] $messagesReceived
* @property-read Collection|Message[] $messagesSent
* @property-read Collection|Message[] $messages
* *
* @method static QueryBuilder|User[] whereId($value) * @method static QueryBuilder|User[] whereId($value)
* @method static QueryBuilder|User[] whereName($value) * @method static QueryBuilder|User[] whereName($value)
@ -50,12 +58,6 @@ use Illuminate\Database\Query\Builder as QueryBuilder;
* @method static QueryBuilder|User[] whereLastLoginAt($value) * @method static QueryBuilder|User[] whereLastLoginAt($value)
* @method static QueryBuilder|User[] whereCreatedAt($value) * @method static QueryBuilder|User[] whereCreatedAt($value)
* @method static QueryBuilder|User[] whereUpdatedAt($value) * @method static QueryBuilder|User[] whereUpdatedAt($value)
*
* @property-read Collection|Question[] $questionsAsked
* @property-read Collection|Question[] $questionsAnswered
* @property-read Collection|Message[] $messagesReceived
* @property-read Collection|Message[] $messagesSent
* @property-read Collection|Message[] $messages
*/ */
class User extends BaseModel class User extends BaseModel
{ {
@ -94,6 +96,14 @@ class User extends BaseModel
->withDefault(); ->withDefault();
} }
/**
* @return BelongsToMany
*/
public function groups(): BelongsToMany
{
return $this->belongsToMany(Group::class, 'users_groups');
}
/** /**
* @return HasOne * @return HasOne
*/ */
@ -104,6 +114,33 @@ class User extends BaseModel
->withDefault(); ->withDefault();
} }
/**
* @return Builder
*/
public function privileges(): Builder
{
/** @var Builder $builder */
$builder = Privilege::query()
->whereIn('id', function ($query) {
/** @var QueryBuilder $query */
$query->select('privilege_id')
->from('group_privileges')
->join('users_groups', 'users_groups.group_id', '=', 'group_privileges.group_id')
->where('users_groups.user_id', '=', $this->id)
->distinct();
});
return $builder;
}
/**
* @return SupportCollection
*/
public function getPrivilegesAttribute(): SupportCollection
{
return $this->privileges()->get();
}
/** /**
* @return HasOne * @return HasOne
*/ */

View File

@ -7,6 +7,7 @@ use Engelsystem\Controllers\HomeController;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Redirector; use Engelsystem\Http\Redirector;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\TestCase; use Engelsystem\Test\Unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
@ -21,7 +22,7 @@ class HomeControllerTest extends TestCase
$config = new Config(['home_site' => '/foo']); $config = new Config(['home_site' => '/foo']);
/** @var Authenticator|MockObject $auth */ /** @var Authenticator|MockObject $auth */
$auth = $this->createMock(Authenticator::class); $auth = $this->createMock(Authenticator::class);
$this->setExpects($auth, 'user', null, true); $this->setExpects($auth, 'user', null, new User());
/** @var Redirector|MockObject $redirect */ /** @var Redirector|MockObject $redirect */
$redirect = $this->createMock(Redirector::class); $redirect = $this->createMock(Redirector::class);
$this->setExpects($redirect, 'to', ['/foo'], new Response()); $this->setExpects($redirect, 'to', ['/foo'], new Response());

View File

@ -20,9 +20,11 @@ class AuthenticatorServiceProviderTest extends ServiceProviderTest
$app = new Application(); $app = new Application();
$app->bind(ServerRequestInterface::class, Request::class); $app->bind(ServerRequestInterface::class, Request::class);
$config = new Config(); $config = new Config([
$config->set('password_algorithm', PASSWORD_DEFAULT); 'password_algorithm' => PASSWORD_DEFAULT,
$config->set('auth_guest_role', 42); 'auth_guest_role' => 42,
'auth_default_role' => 1337,
]);
$app->instance('config', $config); $app->instance('config', $config);
$serviceProvider = new AuthenticatorServiceProvider($app); $serviceProvider = new AuthenticatorServiceProvider($app);
@ -36,5 +38,6 @@ class AuthenticatorServiceProviderTest extends ServiceProviderTest
$auth = $app->get(Authenticator::class); $auth = $app->get(Authenticator::class);
$this->assertEquals(PASSWORD_DEFAULT, $auth->getPasswordAlgorithm()); $this->assertEquals(PASSWORD_DEFAULT, $auth->getPasswordAlgorithm());
$this->assertEquals(42, $auth->getGuestRole()); $this->assertEquals(42, $auth->getGuestRole());
$this->assertEquals(1337, $auth->getDefaultRole());
} }
} }

View File

@ -3,6 +3,8 @@
namespace Engelsystem\Test\Unit\Helpers; namespace Engelsystem\Test\Unit\Helpers;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\Authenticator;
use Engelsystem\Models\Group;
use Engelsystem\Models\Privilege;
use Engelsystem\Models\User\User; use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\HasDatabase; use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Test\Unit\Helpers\Stub\UserModelImplementation; use Engelsystem\Test\Unit\Helpers\Stub\UserModelImplementation;
@ -108,14 +110,23 @@ class AuthenticatorTest extends ServiceProviderTest
*/ */
public function testCan() public function testCan()
{ {
$this->initDatabase();
/** @var ServerRequestInterface|MockObject $request */ /** @var ServerRequestInterface|MockObject $request */
$request = $this->getMockForAbstractClass(ServerRequestInterface::class); $request = $this->getMockForAbstractClass(ServerRequestInterface::class);
/** @var Session|MockObject $session */ /** @var Session|MockObject $session */
$session = $this->createMock(Session::class); $session = $this->createMock(Session::class);
/** @var UserModelImplementation|MockObject $userRepository */ /** @var UserModelImplementation|MockObject $userRepository */
$userRepository = new UserModelImplementation(); $userRepository = new UserModelImplementation();
/** @var User|MockObject $user */ /** @var User $user */
$user = $this->createMock(User::class); $user = User::factory()->create();
/** @var Group $group */
$group = Group::factory()->create();
/** @var Privilege $privilege */
$privilege = Privilege::factory()->create(['name' => 'bar']);
$user->groups()->attach($group);
$group->privileges()->attach($privilege);
$session->expects($this->once()) $session->expects($this->once())
->method('get') ->method('get')
@ -128,20 +139,14 @@ class AuthenticatorTest extends ServiceProviderTest
/** @var Authenticator|MockObject $auth */ /** @var Authenticator|MockObject $auth */
$auth = $this->getMockBuilder(Authenticator::class) $auth = $this->getMockBuilder(Authenticator::class)
->setConstructorArgs([$request, $session, $userRepository]) ->setConstructorArgs([$request, $session, $userRepository])
->onlyMethods(['getPermissionsByGroup', 'getPermissionsByUser', 'user']) ->onlyMethods(['user'])
->getMock(); ->getMock();
$auth->expects($this->exactly(1))
->method('getPermissionsByGroup')
->with(10)
->willReturn([]);
$auth->expects($this->exactly(1))
->method('getPermissionsByUser')
->with($user)
->willReturn(['bar']);
$auth->expects($this->exactly(2)) $auth->expects($this->exactly(2))
->method('user') ->method('user')
->willReturnOnConsecutiveCalls(null, $user); ->willReturnOnConsecutiveCalls(null, $user);
Group::factory()->create(['id' => $auth->getGuestRole()]);
// No user, no permissions // No user, no permissions
$this->assertFalse($auth->can('foo')); $this->assertFalse($auth->can('foo'));
@ -245,6 +250,18 @@ class AuthenticatorTest extends ServiceProviderTest
$this->assertEquals(PASSWORD_ARGON2I, $auth->getPasswordAlgorithm()); $this->assertEquals(PASSWORD_ARGON2I, $auth->getPasswordAlgorithm());
} }
/**
* @covers \Engelsystem\Helpers\Authenticator::setDefaultRole
* @covers \Engelsystem\Helpers\Authenticator::getDefaultRole
*/
public function testDefaultRole()
{
$auth = $this->getAuthenticator();
$auth->setDefaultRole(1337);
$this->assertEquals(1337, $auth->getDefaultRole());
}
/** /**
* @covers \Engelsystem\Helpers\Authenticator::setGuestRole * @covers \Engelsystem\Helpers\Authenticator::setGuestRole
* @covers \Engelsystem\Helpers\Authenticator::getGuestRole * @covers \Engelsystem\Helpers\Authenticator::getGuestRole
@ -262,8 +279,7 @@ class AuthenticatorTest extends ServiceProviderTest
*/ */
protected function getAuthenticator() protected function getAuthenticator()
{ {
return new class extends Authenticator return new class extends Authenticator {
{
/** @noinspection PhpMissingParentConstructorInspection */ /** @noinspection PhpMissingParentConstructorInspection */
public function __construct() public function __construct()
{ {

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Test\Unit\Models;
use Engelsystem\Models\Group;
use Engelsystem\Models\Privilege;
use Engelsystem\Models\User\User;
class GroupTest extends ModelTest
{
/**
* @covers \Engelsystem\Models\Group::privileges
*/
public function testPrivileges(): void
{
/** @var Privilege $privilege1 */
$privilege1 = Privilege::factory()->create();
/** @var Privilege $privilege2 */
$privilege2 = Privilege::factory()->create();
$model = new Group();
$model->name = 'Some Group';
$model->save();
$model->privileges()->attach($privilege1);
$model->privileges()->attach($privilege2);
/** @var Group $savedModel */
$savedModel = Group::first();
$this->assertEquals('Some Group', $savedModel->name);
$this->assertEquals($privilege1->name, $savedModel->privileges[0]->name);
$this->assertEquals($privilege2->name, $savedModel->privileges[1]->name);
}
/**
* @covers \Engelsystem\Models\Group::users
*/
public function testUsers(): void
{
/** @var User $user1 */
$user1 = User::factory()->create();
/** @var User $user2 = */
$user2 = User::factory()->create();
$model = new Group();
$model->name = 'Some Group';
$model->save();
$model->users()->attach($user1);
$model->users()->attach($user2);
/** @var Group $savedModel */
$savedModel = Group::first();
$this->assertEquals($user1->name, $savedModel->users[0]->name);
$this->assertEquals($user2->name, $savedModel->users[1]->name);
}
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Test\Unit\Models;
use Engelsystem\Models\Group;
use Engelsystem\Models\Privilege;
class PrivilegeTest extends ModelTest
{
/**
* @covers \Engelsystem\Models\Privilege::groups
*/
public function testGroups(): void
{
/** @var Group $group1 */
$group1 = Group::factory()->create();
/** @var Group $group2 */
$group2 = Group::factory()->create();
$model = new Privilege();
$model->name = 'Some Privilege';
$model->description = 'Some long description';
$model->save();
$model->groups()->attach($group1);
$model->groups()->attach($group2);
/** @var Privilege $savedModel */
$savedModel = Privilege::first();
$this->assertEquals('Some Privilege', $savedModel->name);
$this->assertEquals('Some long description', $savedModel->description);
$this->assertEquals($group1->name, $savedModel->groups[0]->name);
$this->assertEquals($group2->name, $savedModel->groups[1]->name);
}
}

View File

@ -5,9 +5,11 @@ namespace Engelsystem\Test\Unit\Models\User;
use Carbon\Carbon; use Carbon\Carbon;
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
use Engelsystem\Models\BaseModel; use Engelsystem\Models\BaseModel;
use Engelsystem\Models\Group;
use Engelsystem\Models\News; use Engelsystem\Models\News;
use Engelsystem\Models\NewsComment; use Engelsystem\Models\NewsComment;
use Engelsystem\Models\OAuth; use Engelsystem\Models\OAuth;
use Engelsystem\Models\Privilege;
use Engelsystem\Models\Question; use Engelsystem\Models\Question;
use Engelsystem\Models\User\Contact; use Engelsystem\Models\User\Contact;
use Engelsystem\Models\User\HasUserModel; use Engelsystem\Models\User\HasUserModel;
@ -19,6 +21,7 @@ use Engelsystem\Models\User\User;
use Engelsystem\Models\Worklog; use Engelsystem\Models\Worklog;
use Engelsystem\Test\Unit\Models\ModelTest; use Engelsystem\Test\Unit\Models\ModelTest;
use Exception; use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class UserTest extends ModelTest class UserTest extends ModelTest
@ -81,60 +84,6 @@ class UserTest extends ModelTest
]; ];
} }
/**
* @covers \Engelsystem\Models\User\User::contact
* @covers \Engelsystem\Models\User\User::license
* @covers \Engelsystem\Models\User\User::personalData
* @covers \Engelsystem\Models\User\User::settings
* @covers \Engelsystem\Models\User\User::state
*
* @dataProvider hasOneRelationsProvider
*
* @param string $class
* @param string $name
* @param array $data
* @throws Exception
*/
public function testHasOneRelations($class, $name, $data)
{
$user = new User($this->data);
$user->save();
/** @var HasUserModel $contact */
$contact = new $class($data);
$contact->user()
->associate($user)
->save();
$this->assertArraySubset($data, (array)$user->{$name}->attributesToArray());
}
/**
* @covers \Engelsystem\Models\User\User::news()
*
* @dataProvider hasManyRelationsProvider
*
* @param string $class Class name of the related models
* @param string $name Name of the accessor for the related models
* @param array $modelData List of the related models
*/
public function testHasManyRelations(string $class, string $name, array $modelData): void
{
$user = new User($this->data);
$user->save();
$relatedModelIds = [];
foreach ($modelData as $data) {
/** @var BaseModel $model */
$model = $this->app->make($class);
$stored = $model->create($data + ['user_id' => $user->id]);
$relatedModelIds[] = $stored->id;
}
$this->assertEquals($relatedModelIds, $user->{$name}->modelKeys());
}
/** /**
* @return array[] * @return array[]
*/ */
@ -160,6 +109,156 @@ class UserTest extends ModelTest
]; ];
} }
/**
* @return array[]
*/
public function belongsToManyRelationsProvider(): array
{
return [
'groups' => [
Group::class,
'groups',
[
[
'name' => 'Lorem',
],
[
'name' => 'Ipsum',
]
]
]
];
}
/**
* @covers \Engelsystem\Models\User\User::contact
* @covers \Engelsystem\Models\User\User::license
* @covers \Engelsystem\Models\User\User::personalData
* @covers \Engelsystem\Models\User\User::settings
* @covers \Engelsystem\Models\User\User::state
*
* @dataProvider hasOneRelationsProvider
*
* @param string $class
* @param string $name
* @param array $data
* @throws Exception
*/
public function testHasOneRelations($class, $name, $data)
{
$user = new User($this->data);
$user->save();
/** @var HasUserModel $instance */
$instance = new $class($data);
$instance->user()
->associate($user)
->save();
$this->assertArraySubset($data, (array)$user->{$name}->attributesToArray());
}
/**
* @covers \Engelsystem\Models\User\User::news()
*
* @dataProvider hasManyRelationsProvider
*
* @param string $class Class name of the related models
* @param string $name Name of the accessor for the related models
* @param array $modelData List of the related models
*/
public function testHasManyRelations(string $class, string $name, array $modelData): void
{
$user = new User($this->data);
$user->save();
$relatedModelIds = [];
foreach ($modelData as $data) {
/** @var BaseModel $model */
$model = $this->app->make($class);
$stored = $model->create($data + ['user_id' => $user->id]);
$relatedModelIds[] = $stored->id;
}
$this->assertEquals($relatedModelIds, $user->{$name}->modelKeys());
}
/**
* @covers \Engelsystem\Models\User\User::groups
*
* @dataProvider belongsToManyRelationsProvider
*
* @param string $class Class name of the related models
* @param string $name Name of the accessor for the related models
* @param array $modelData List of the related models
*/
public function testBelongsToManyRelations(string $class, string $name, array $modelData): void
{
$user = new User($this->data);
$user->save();
$relatedModelIds = [];
foreach ($modelData as $data) {
/** @var BaseModel $model */
$model = $this->app->make($class);
$stored = $model->create($data);
$stored->users()->attach($user);
$relatedModelIds[] = $stored->id;
}
$this->assertEquals($relatedModelIds, $user->{$name}->modelKeys());
}
/**
* @covers \Engelsystem\Models\User\User::privileges
* @covers \Engelsystem\Models\User\User::getPrivilegesAttribute
*/
public function testPrivileges()
{
$user = new User($this->data);
$user->save();
/** @var Group $group1 */
$group1 = Group::factory()->create();
/** @var Group $group2 */
$group2 = Group::factory()->create();
/** @var Group $group3 */
$group3 = Group::factory()->create();
/** @var Privilege $privilege1 */
$privilege1 = Privilege::factory()->create();
/** @var Privilege $privilege2 */
$privilege2 = Privilege::factory()->create();
/** @var Privilege $privilege3 */
$privilege3 = Privilege::factory()->create();
/** @var Privilege $privilege4 */
$privilege4 = Privilege::factory()->create();
$user->groups()->attach($group1);
$user->groups()->attach($group2);
$group1->privileges()->attach($privilege1);
$group1->privileges()->attach($privilege2);
$group2->privileges()->attach($privilege2);
$group2->privileges()->attach($privilege3);
$group3->privileges()->attach($privilege3);
$group3->privileges()->attach($privilege4);
/** @var User $createdUser */
$createdUser = User::first();
$this->assertInstanceOf(Builder::class, $createdUser->privileges());
$privileges = $createdUser->privileges->pluck('name');
$this->assertCount(3, $privileges);
$this->assertContains($privilege1->name, $privileges);
$this->assertContains($privilege2->name, $privileges);
$this->assertContains($privilege3->name, $privileges);
}
/** /**
* Tests that accessing the NewsComments of an User works. * Tests that accessing the NewsComments of an User works.
* *