From ddbb0ba87025ab3ba8f91d6931208a857ea66cb3 Mon Sep 17 00:00:00 2001 From: MGatner Date: Wed, 13 Jul 2022 16:52:37 +0000 Subject: [PATCH 1/2] Add alternate Models with Entities --- docs/_changes.md | 4 +- docs/extending.md | 7 + phpstan.neon.dist | 1 + src/Authorization/GroupModel.php | 197 +---------------------- src/Authorization/PermissionModel.php | 98 +----------- src/Entities/Group.php | 18 +++ src/Entities/Permission.php | 18 +++ src/Models/GroupModel.php | 217 ++++++++++++++++++++++++++ src/Models/PermissionModel.php | 112 +++++++++++++ 9 files changed, 389 insertions(+), 283 deletions(-) create mode 100644 src/Entities/Group.php create mode 100644 src/Entities/Permission.php create mode 100644 src/Models/GroupModel.php create mode 100644 src/Models/PermissionModel.php diff --git a/docs/_changes.md b/docs/_changes.md index 06cf739..a2c9682 100644 --- a/docs/_changes.md +++ b/docs/_changes.md @@ -2,7 +2,9 @@ ## Unreleased -_No unreleased changes yet_ +Enhancements: + +- Added alternate authorization Models with stronger typing ## 1.1.0 diff --git a/docs/extending.md b/docs/extending.md index 8f88015..63bde81 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -26,6 +26,13 @@ Entities with your own casts and class methods. If you extend the Model and supply your own validation rules you can also enforce those on the `AuthController` by providing a `$registrationRules` property in **app/Config/Validation.php**. +### Alternate Models + +Since version `1.2.0` this library includes an alternate version of the authorization models +that use specific Entities as their return types. These models are not yet used by the library +itself for backwards-compatibility, but they are highly recommended for any implementing +projects or model extensions. + ## Views Myth:Auth uses its own views by default, but you may want to update these in order to change diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 146430f..4c3ec14 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,6 +10,7 @@ parameters: - src/Config/Routes.php - src/Views/* ignoreErrors: + - '#.+deprecated class Myth\\Auth\\Authorization\\(Group|Permission)Model.+#' - '#.+Mockery.+#' - '#Call to an undefined method .+::shouldReceive\(\)#' - '#Call to an undefined static method Config\\Services::[A-Za-z]+\(\)#' diff --git a/src/Authorization/GroupModel.php b/src/Authorization/GroupModel.php index 6aa3044..5aa3b98 100644 --- a/src/Authorization/GroupModel.php +++ b/src/Authorization/GroupModel.php @@ -2,196 +2,13 @@ namespace Myth\Auth\Authorization; -use CodeIgniter\Model; +use Myth\Auth\Models\GroupModel as BaseModel; -class GroupModel extends Model +/** + * @deprecated 1.2.0 Use Myth\Auth\Models\GroupModel instead + */ +class GroupModel extends BaseModel { - protected $table = 'auth_groups'; - protected $primaryKey = 'id'; - protected $returnType = 'object'; - protected $allowedFields = [ - 'name', 'description', - ]; - protected $useTimestamps = false; - protected $validationRules = [ - 'name' => 'required|max_length[255]|is_unique[auth_groups.name,name,{name}]', - 'description' => 'max_length[255]', - ]; - protected $validationMessages = []; - protected $skipValidation = false; - - //-------------------------------------------------------------------- - // Users - //-------------------------------------------------------------------- - - /** - * Adds a single user to a single group. - * - * @return bool - */ - public function addUserToGroup(int $userId, int $groupId) - { - cache()->delete("{$groupId}_users"); - cache()->delete("{$userId}_groups"); - cache()->delete("{$userId}_permissions"); - - $data = [ - 'user_id' => $userId, - 'group_id' => $groupId, - ]; - - return (bool) $this->db->table('auth_groups_users')->insert($data); - } - - /** - * Removes a single user from a single group. - * - * @param int|string $groupId - * - * @return bool - */ - public function removeUserFromGroup(int $userId, $groupId) - { - cache()->delete("{$groupId}_users"); - cache()->delete("{$userId}_groups"); - cache()->delete("{$userId}_permissions"); - - return $this->db->table('auth_groups_users') - ->where([ - 'user_id' => $userId, - 'group_id' => (int) $groupId, - ])->delete(); - } - - /** - * Removes a single user from all groups. - * - * @return bool - */ - public function removeUserFromAllGroups(int $userId) - { - cache()->delete("{$userId}_groups"); - cache()->delete("{$userId}_permissions"); - - return $this->db->table('auth_groups_users') - ->where('user_id', $userId) - ->delete(); - } - - /** - * Returns an array of all groups that a user is a member of. - * - * @return array - */ - public function getGroupsForUser(int $userId) - { - if (null === $found = cache("{$userId}_groups")) { - $found = $this->builder() - ->select('auth_groups_users.*, auth_groups.name, auth_groups.description') - ->join('auth_groups_users', 'auth_groups_users.group_id = auth_groups.id', 'left') - ->where('user_id', $userId) - ->get()->getResultArray(); - - cache()->save("{$userId}_groups", $found, 300); - } - - return $found; - } - - /** - * Returns an array of all users that are members of a group. - * - * @return array - */ - public function getUsersForGroup(int $groupId) - { - if (null === $found = cache("{$groupId}_users")) { - $found = $this->builder() - ->select('auth_groups_users.*, users.*') - ->join('auth_groups_users', 'auth_groups_users.group_id = auth_groups.id', 'left') - ->join('users', 'auth_groups_users.user_id = users.id', 'left') - ->where('auth_groups.id', $groupId) - ->get()->getResultArray(); - - cache()->save("{$groupId}_users", $found, 300); - } - - return $found; - } - - //-------------------------------------------------------------------- - // Permissions - //-------------------------------------------------------------------- - - /** - * Gets all permissions for a group in a way that can be - * easily used to check against: - * - * [ - * id => name, - * id => name - * ] - */ - public function getPermissionsForGroup(int $groupId): array - { - $permissionModel = model(PermissionModel::class); - $fromGroup = $permissionModel - ->select('auth_permissions.*') - ->join('auth_groups_permissions', 'auth_groups_permissions.permission_id = auth_permissions.id', 'inner') - ->where('group_id', $groupId) - ->findAll(); - - $found = []; - - foreach ($fromGroup as $permission) { - $found[$permission['id']] = $permission; - } - - return $found; - } - - /** - * Add a single permission to a single group, by IDs. - * - * @return mixed - */ - public function addPermissionToGroup(int $permissionId, int $groupId) - { - $data = [ - 'permission_id' => $permissionId, - 'group_id' => $groupId, - ]; - - return $this->db->table('auth_groups_permissions')->insert($data); - } - - //-------------------------------------------------------------------- - - /** - * Removes a single permission from a single group. - * - * @return mixed - */ - public function removePermissionFromGroup(int $permissionId, int $groupId) - { - return $this->db->table('auth_groups_permissions') - ->where([ - 'permission_id' => $permissionId, - 'group_id' => $groupId, - ])->delete(); - } - - //-------------------------------------------------------------------- - - /** - * Removes a single permission from all groups. - * - * @return mixed - */ - public function removePermissionFromAllGroups(int $permissionId) - { - return $this->db->table('auth_groups_permissions') - ->where('permission_id', $permissionId) - ->delete(); - } + protected $returnType = 'object'; + protected string $permissionModel = PermissionModel::class; } diff --git a/src/Authorization/PermissionModel.php b/src/Authorization/PermissionModel.php index 376dfcb..fe0ba4a 100644 --- a/src/Authorization/PermissionModel.php +++ b/src/Authorization/PermissionModel.php @@ -2,98 +2,12 @@ namespace Myth\Auth\Authorization; -use CodeIgniter\Database\BaseResult; -use CodeIgniter\Database\Query; -use CodeIgniter\Model; +use Myth\Auth\Models\PermissionModel as BaseModel; -class PermissionModel extends Model +/** + * @deprecated 1.2.0 Use Myth\Auth\Models\PermissionModel instead + */ +class PermissionModel extends BaseModel { - protected $table = 'auth_permissions'; - protected $allowedFields = [ - 'name', 'description', - ]; - protected $useTimestamps = false; - protected $validationRules = [ - 'name' => 'required|max_length[255]|is_unique[auth_permissions.name,name,{name}]', - 'description' => 'max_length[255]', - ]; - - /** - * Checks to see if a user, or one of their groups, - * has a specific permission. - */ - public function doesUserHavePermission(int $userId, int $permissionId): bool - { - return array_key_exists($permissionId, $this->getPermissionsForUser($userId)); - } - - /** - * Adds a single permission to a single user. - * - * @return BaseResult|false|Query - */ - public function addPermissionToUser(int $permissionId, int $userId) - { - cache()->delete("{$userId}_permissions"); - - return $this->db->table('auth_users_permissions')->insert([ - 'user_id' => $userId, - 'permission_id' => $permissionId, - ]); - } - - /** - * Removes a permission from a user. - * - * @return mixed - */ - public function removePermissionFromUser(int $permissionId, int $userId) - { - $this->db->table('auth_users_permissions')->where([ - 'user_id' => $userId, - 'permission_id' => $permissionId, - ])->delete(); - - cache()->delete("{$userId}_permissions"); - } - - /** - * Gets all permissions for a user in a way that can be - * easily used to check against: - * - * [ - * id => name, - * id => name - * ] - */ - public function getPermissionsForUser(int $userId): array - { - if (null === $found = cache("{$userId}_permissions")) { - $fromUser = $this->db->table('auth_users_permissions') - ->select('id, auth_permissions.name') - ->join('auth_permissions', 'auth_permissions.id = permission_id', 'inner') - ->where('user_id', $userId) - ->get() - ->getResultObject(); - $fromGroup = $this->db->table('auth_groups_users') - ->select('auth_permissions.id, auth_permissions.name') - ->join('auth_groups_permissions', 'auth_groups_permissions.group_id = auth_groups_users.group_id', 'inner') - ->join('auth_permissions', 'auth_permissions.id = auth_groups_permissions.permission_id', 'inner') - ->where('user_id', $userId) - ->get() - ->getResultObject(); - - $combined = array_merge($fromUser, $fromGroup); - - $found = []; - - foreach ($combined as $row) { - $found[$row->id] = strtolower($row->name); - } - - cache()->save("{$userId}_permissions", $found, 300); - } - - return $found; - } + protected $returnType = 'array'; } diff --git a/src/Entities/Group.php b/src/Entities/Group.php new file mode 100644 index 0000000..2d53e73 --- /dev/null +++ b/src/Entities/Group.php @@ -0,0 +1,18 @@ + 'required|max_length[255]|is_unique[auth_groups.name,name,{name}]', + 'description' => 'max_length[255]', + ]; + + /** + * The permission model to use. + * + * @see getPermissionsForGroup() + */ + protected string $permissionModel = PermissionModel::class; + + //-------------------------------------------------------------------- + // Users + //-------------------------------------------------------------------- + + /** + * Adds a single user to a single group. + * + * @return bool + */ + public function addUserToGroup(int $userId, int $groupId) + { + cache()->delete("{$groupId}_users"); + cache()->delete("{$userId}_groups"); + cache()->delete("{$userId}_permissions"); + + $data = [ + 'user_id' => $userId, + 'group_id' => $groupId, + ]; + + return (bool) $this->db->table('auth_groups_users')->insert($data); + } + + /** + * Removes a single user from a single group. + * + * @param int|string $groupId + * + * @return bool + */ + public function removeUserFromGroup(int $userId, $groupId) + { + cache()->delete("{$groupId}_users"); + cache()->delete("{$userId}_groups"); + cache()->delete("{$userId}_permissions"); + + return $this->db->table('auth_groups_users') + ->where([ + 'user_id' => $userId, + 'group_id' => (int) $groupId, + ])->delete(); + } + + /** + * Removes a single user from all groups. + * + * @return bool + */ + public function removeUserFromAllGroups(int $userId) + { + cache()->delete("{$userId}_groups"); + cache()->delete("{$userId}_permissions"); + + return $this->db->table('auth_groups_users') + ->where('user_id', $userId) + ->delete(); + } + + /** + * Returns an array of all groups that a user is a member of. + * + * @return array[] + */ + public function getGroupsForUser(int $userId) + { + if (null === $found = cache("{$userId}_groups")) { + $found = $this->builder() + ->select('auth_groups_users.*, auth_groups.name, auth_groups.description') + ->join('auth_groups_users', 'auth_groups_users.group_id = auth_groups.id', 'left') + ->where('user_id', $userId) + ->get()->getResultArray(); + + cache()->save("{$userId}_groups", $found, 300); + } + + return $found; + } + + /** + * Returns an array of all users that are members of a group. + * + * @return array[] + */ + public function getUsersForGroup(int $groupId) + { + if (null === $found = cache("{$groupId}_users")) { + $found = $this->builder() + ->select('auth_groups_users.*, users.*') + ->join('auth_groups_users', 'auth_groups_users.group_id = auth_groups.id', 'left') + ->join('users', 'auth_groups_users.user_id = users.id', 'left') + ->where('auth_groups.id', $groupId) + ->get()->getResultArray(); + + cache()->save("{$groupId}_users", $found, 300); + } + + return $found; + } + + //-------------------------------------------------------------------- + // Permissions + //-------------------------------------------------------------------- + + /** + * Gets all permissions for a group in a way that can be + * easily used to check against: + * + * @return array An array in format permissionId => permission + */ + public function getPermissionsForGroup(int $groupId): array + { + $fromGroup = model($this->permissionModel) + ->select('auth_permissions.*') + ->join('auth_groups_permissions', 'auth_groups_permissions.permission_id = auth_permissions.id', 'inner') + ->where('group_id', $groupId) + ->findAll(); + + $found = []; + + foreach ($fromGroup as $permission) { + $id = is_object($permission) ? $permission->id : $permission['id']; + + $found[$id] = $permission; + } + + return $found; + } + + /** + * Add a single permission to a single group, by IDs. + * + * @return mixed + */ + public function addPermissionToGroup(int $permissionId, int $groupId) + { + $data = [ + 'permission_id' => $permissionId, + 'group_id' => $groupId, + ]; + + return $this->db->table('auth_groups_permissions')->insert($data); + } + + //-------------------------------------------------------------------- + + /** + * Removes a single permission from a single group. + * + * @return mixed + */ + public function removePermissionFromGroup(int $permissionId, int $groupId) + { + return $this->db->table('auth_groups_permissions') + ->where([ + 'permission_id' => $permissionId, + 'group_id' => $groupId, + ])->delete(); + } + + //-------------------------------------------------------------------- + + /** + * Removes a single permission from all groups. + * + * @return mixed + */ + public function removePermissionFromAllGroups(int $permissionId) + { + return $this->db->table('auth_groups_permissions') + ->where('permission_id', $permissionId) + ->delete(); + } + + /** + * Faked data for Fabricator. + * + * @return Group|stdClass See GroupFaker + */ + public function fake(Generator &$faker) + { + return new Group([ + 'name' => $faker->word, + 'description' => $faker->sentence, + ]); + } +} diff --git a/src/Models/PermissionModel.php b/src/Models/PermissionModel.php new file mode 100644 index 0000000..325f961 --- /dev/null +++ b/src/Models/PermissionModel.php @@ -0,0 +1,112 @@ + 'required|max_length[255]|is_unique[auth_permissions.name,name,{name}]', + 'description' => 'max_length[255]', + ]; + + /** + * Checks to see if a user, or one of their groups, + * has a specific permission. + */ + public function doesUserHavePermission(int $userId, int $permissionId): bool + { + return array_key_exists($permissionId, $this->getPermissionsForUser($userId)); + } + + /** + * Adds a single permission to a single user. + * + * @return BaseResult|false|Query + */ + public function addPermissionToUser(int $permissionId, int $userId) + { + cache()->delete("{$userId}_permissions"); + + return $this->db->table('auth_users_permissions')->insert([ + 'user_id' => $userId, + 'permission_id' => $permissionId, + ]); + } + + /** + * Removes a permission from a user. + * + * @return bool + */ + public function removePermissionFromUser(int $permissionId, int $userId) + { + cache()->delete("{$userId}_permissions"); + + return $this->db->table('auth_users_permissions')->where([ + 'user_id' => $userId, + 'permission_id' => $permissionId, + ])->delete(); + } + + /** + * Gets all permissions for a user in a way that can be + * easily used to check against: + * + * @return array An array in format permissionId => permissionName + */ + public function getPermissionsForUser(int $userId): array + { + if (null === $found = cache("{$userId}_permissions")) { + $fromUser = $this->db->table('auth_users_permissions') + ->select('id, auth_permissions.name') + ->join('auth_permissions', 'auth_permissions.id = permission_id', 'inner') + ->where('user_id', $userId) + ->get() + ->getResultObject(); + $fromGroup = $this->db->table('auth_groups_users') + ->select('auth_permissions.id, auth_permissions.name') + ->join('auth_groups_permissions', 'auth_groups_permissions.group_id = auth_groups_users.group_id', 'inner') + ->join('auth_permissions', 'auth_permissions.id = auth_groups_permissions.permission_id', 'inner') + ->where('user_id', $userId) + ->get() + ->getResultObject(); + + $combined = array_merge($fromUser, $fromGroup); + + $found = []; + + foreach ($combined as $row) { + $found[$row->id] = strtolower($row->name); + } + + cache()->save("{$userId}_permissions", $found, 300); + } + + return $found; + } + + /** + * Faked data for Fabricator. + * + * @return array|Permission See PermissionFaker + */ + public function fake(Generator &$faker) + { + return new Permission([ + 'name' => $faker->word, + 'description' => $faker->sentence, + ]); + } +} From f637622b98ad405aeab2472788c87b138f49a256 Mon Sep 17 00:00:00 2001 From: MGatner Date: Wed, 13 Jul 2022 16:58:33 +0000 Subject: [PATCH 2/2] Add remaining fake method --- src/Models/UserModel.php | 13 +++++++++++++ src/Test/Fakers/UserFaker.php | 13 ------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Models/UserModel.php b/src/Models/UserModel.php index d89e1da..29b6e42 100644 --- a/src/Models/UserModel.php +++ b/src/Models/UserModel.php @@ -3,6 +3,7 @@ namespace Myth\Auth\Models; use CodeIgniter\Model; +use Faker\Generator; use Myth\Auth\Authorization\GroupModel; use Myth\Auth\Entities\User; @@ -108,4 +109,16 @@ protected function addToGroup($data) return $data; } + + /** + * Faked data for Fabricator. + */ + public function fake(Generator &$faker): User + { + return new User([ + 'email' => $faker->email, + 'username' => $faker->userName, + 'password' => bin2hex(random_bytes(16)), + ]); + } } diff --git a/src/Test/Fakers/UserFaker.php b/src/Test/Fakers/UserFaker.php index e0555d5..142f61c 100644 --- a/src/Test/Fakers/UserFaker.php +++ b/src/Test/Fakers/UserFaker.php @@ -2,21 +2,8 @@ namespace Myth\Auth\Test\Fakers; -use Faker\Generator; -use Myth\Auth\Entities\User; use Myth\Auth\Models\UserModel; class UserFaker extends UserModel { - /** - * Faked data for Fabricator. - */ - public function fake(Generator &$faker): User - { - return new User([ - 'email' => $faker->email, - 'username' => $faker->userName, - 'password' => bin2hex(random_bytes(16)), - ]); - } }