From 3e8fd62335d783edee7a939e4114009bb79e0750 Mon Sep 17 00:00:00 2001 From: Andrey Utkin Date: Tue, 22 Apr 2014 11:10:25 +0700 Subject: [PATCH] Initial commit --- Rbac/IOwned.php | 14 + Rbac/IRbacService.php | 14 + Rbac/ISubject.php | 7 + Rbac/Impl/Memory/RbacService.php | 79 +++++ Rbac/Impl/Repository/Dao/DRbacPermission.php | 42 +++ .../Dao/DRbacPermissionHierarchy.php | 31 ++ Rbac/Impl/Repository/Dao/DRbacRole.php | 73 +++++ .../Repository/Dao/DRbacRoleHierarchy.php | 28 ++ .../Repository/Dao/DRbacRolesPermissions.php | 40 +++ Rbac/Impl/Repository/Dao/DRbacUserRoles.php | 41 +++ Rbac/Impl/Repository/IRbacDaoFactory.php | 54 ++++ Rbac/Impl/Repository/IRbacService.php | 62 ++++ Rbac/Impl/Repository/PermissionFilter.php | 38 +++ Rbac/Impl/Repository/RbacCache.php | 173 +++++++++++ Rbac/Impl/Repository/RbacService.php | 272 ++++++++++++++++++ Rbac/Impl/Repository/RoleFilter.php | 68 +++++ Rbac/OwnerPermission.php | 10 + Rbac/Permission.php | 45 +++ Rbac/Role.php | 28 ++ composer.json | 22 ++ 20 files changed, 1141 insertions(+) create mode 100644 Rbac/IOwned.php create mode 100644 Rbac/IRbacService.php create mode 100644 Rbac/ISubject.php create mode 100644 Rbac/Impl/Memory/RbacService.php create mode 100644 Rbac/Impl/Repository/Dao/DRbacPermission.php create mode 100644 Rbac/Impl/Repository/Dao/DRbacPermissionHierarchy.php create mode 100644 Rbac/Impl/Repository/Dao/DRbacRole.php create mode 100644 Rbac/Impl/Repository/Dao/DRbacRoleHierarchy.php create mode 100644 Rbac/Impl/Repository/Dao/DRbacRolesPermissions.php create mode 100644 Rbac/Impl/Repository/Dao/DRbacUserRoles.php create mode 100644 Rbac/Impl/Repository/IRbacDaoFactory.php create mode 100644 Rbac/Impl/Repository/IRbacService.php create mode 100644 Rbac/Impl/Repository/PermissionFilter.php create mode 100644 Rbac/Impl/Repository/RbacCache.php create mode 100644 Rbac/Impl/Repository/RbacService.php create mode 100644 Rbac/Impl/Repository/RoleFilter.php create mode 100644 Rbac/OwnerPermission.php create mode 100644 Rbac/Permission.php create mode 100644 Rbac/Role.php create mode 100644 composer.json diff --git a/Rbac/IOwned.php b/Rbac/IOwned.php new file mode 100644 index 0000000..b5e73f3 --- /dev/null +++ b/Rbac/IOwned.php @@ -0,0 +1,14 @@ +roles = $roles; + $this->permissions = $permissions; + } + + /** + * Assumed there is no recursive roles. + * + * @param string $role + * @return Role[] + */ + private function flatRole($role) + { + $res = array(); + foreach ((array)$role as $each) { + $res[] = $this->roles[$each]; + foreach ($this->roles[$each]->getChildren() as $child) { + $res = array_merge($res, $this->flatRole($child)); + } + } + return $res; + } + + /** + * Assumed there is no recursive permission. + * + * @param string $perm + * @return Permission[] + */ + private function flatPermission($perm) + { + $res[] = $this->permissions[$perm]; + foreach ($this->permissions[$perm]->getChildren() as $child) { + $res = array_merge($res, $this->flatPermission($child)); + } + return $res; + } + + public function checkPermission(ISubject $subject, Permission $permission, array $params = array()) + { + $roles = $this->flatRole($subject->getRoles()); + $perms = $this->flatPermission($perm); + + foreach ($roles as $role) { + foreach ($perms as $permission) { + if ($this->roles[$role->getName()]->hasPermission($permission->getName())) { + if ($permission->check($subject, $object)) { + return true; + } + } + } + } + return false; + } +} diff --git a/Rbac/Impl/Repository/Dao/DRbacPermission.php b/Rbac/Impl/Repository/Dao/DRbacPermission.php new file mode 100644 index 0000000..b0449c4 --- /dev/null +++ b/Rbac/Impl/Repository/Dao/DRbacPermission.php @@ -0,0 +1,42 @@ +id = new Field(Type::STRING); + + $this->_primaryKey = new PrimaryKey('id'); + + parent::__construct($name, $alias, true); + } +} diff --git a/Rbac/Impl/Repository/Dao/DRbacPermissionHierarchy.php b/Rbac/Impl/Repository/Dao/DRbacPermissionHierarchy.php new file mode 100644 index 0000000..0286650 --- /dev/null +++ b/Rbac/Impl/Repository/Dao/DRbacPermissionHierarchy.php @@ -0,0 +1,31 @@ + 'id') + * ); + */ + public $_fkRbacPermission; + + public function __construct($name, $alias = 'PermissionHierarchy') + { + $this->childId = new Field(Type::STRING); + $this->parentId = new Field(Type::STRING); + + parent::__construct($name, $alias, true); + } +} + + diff --git a/Rbac/Impl/Repository/Dao/DRbacRole.php b/Rbac/Impl/Repository/Dao/DRbacRole.php new file mode 100644 index 0000000..2ee7671 --- /dev/null +++ b/Rbac/Impl/Repository/Dao/DRbacRole.php @@ -0,0 +1,73 @@ + 'childId') + * ); + */ + public $_fkRbacRoleHierarchy; + + /** + * new OneToMany( + * 'Project\Dao\DRbacUserRoles', + * array('id' => 'roleId') + * ); + */ + public $_fkRbacUserRoles; + + /** + * new OneToMany( + * 'Project\Dao\DRbacRolesPermissions', + * array('id' => 'roleId') + * ); + */ + public $_fkRbacRolesPermissions; + + /** + * new ManyToMany( + * 'Project\Dao\DUser', + * 'Project\Dao\DRbacUserRoles' + * ); + */ + public $_fkUser; + + /** + * new ManyToMany( + * 'Project\Dao\DRbacRole', + * 'Project\Dao\DRbacRoleHierarchy' + * ); + */ + public $_fkParent; + + /** + * new ManyToMany( + * 'Project\Dao\DRbacRole', + * 'Project\Dao\DRbacRoleHierarchy' + * ); + */ + public $_fkChild; + + public $_primaryKey; + + public $_entityClass = 'Vda\Security\Rbac\Role'; + + public function __construct($table, $alias = 'role') + { + $this->id = new Field(Type::STRING); + + $this->_primaryKey = new PrimaryKey('id'); + + parent::__construct($table, $alias, true); + } +} diff --git a/Rbac/Impl/Repository/Dao/DRbacRoleHierarchy.php b/Rbac/Impl/Repository/Dao/DRbacRoleHierarchy.php new file mode 100644 index 0000000..2a4055b --- /dev/null +++ b/Rbac/Impl/Repository/Dao/DRbacRoleHierarchy.php @@ -0,0 +1,28 @@ + 'id') + * ); + */ + public $_fkRbacRole; + + public function __construct($table, $alias = 'RoleHierarchy') + { + $this->childId = new Field(Type::STRING); + $this->parentId = new Field(Type::STRING); + + parent::__construct($table, $alias, true); + } +} diff --git a/Rbac/Impl/Repository/Dao/DRbacRolesPermissions.php b/Rbac/Impl/Repository/Dao/DRbacRolesPermissions.php new file mode 100644 index 0000000..51132a5 --- /dev/null +++ b/Rbac/Impl/Repository/Dao/DRbacRolesPermissions.php @@ -0,0 +1,40 @@ + 'id') + * ); + */ + public $_fkRbacRole; + + /** + * new ManyToOne( + * 'Project\Dao\DRbacPermission', + * array('permissionId' => 'id') + * ); + */ + public $_fkRbacPermission; + + public function __construct($table, $alias = 'RolesPermissions') + { + $this->roleId = new Field(Type::STRING); + $this->permissionId = new Field(Type::STRING); + + $this->_primaryKey = new PrimaryKey('roleId', 'permissionId'); + + parent::__construct($table, $alias, true); + } +} + diff --git a/Rbac/Impl/Repository/Dao/DRbacUserRoles.php b/Rbac/Impl/Repository/Dao/DRbacUserRoles.php new file mode 100644 index 0000000..4fcaf5b --- /dev/null +++ b/Rbac/Impl/Repository/Dao/DRbacUserRoles.php @@ -0,0 +1,41 @@ + 'id') + * ); + */ + public $_fkRbacRole; + + /** + * new ManyToOne( + * 'Project\Dao\DUser', + * array('userId' => 'userId') + * ); + */ + public $_fkUser; + + public function __construct($table, $alias = 'UserRoles') + { + $this->roleId = new Field(Type::STRING); + $this->userId = new Field(Type::INTEGER); + + $this->_primaryKey = new PrimaryKey('roleId', 'userId'); + + parent::__construct($table, $alias, true); + } +} diff --git a/Rbac/Impl/Repository/IRbacDaoFactory.php b/Rbac/Impl/Repository/IRbacDaoFactory.php new file mode 100644 index 0000000..413c9f7 --- /dev/null +++ b/Rbac/Impl/Repository/IRbacDaoFactory.php @@ -0,0 +1,54 @@ +permission = $permission; + $this->role = $role; + } + + public function permission() + { + return $this->permission; + } + + public function isRoleInvolved() + { + return $this->isRoleInvolved; + } + + public function role() + { + $this->isRoleInvolved = true; + return $this->role; + } +} diff --git a/Rbac/Impl/Repository/RbacCache.php b/Rbac/Impl/Repository/RbacCache.php new file mode 100644 index 0000000..09e6b7e --- /dev/null +++ b/Rbac/Impl/Repository/RbacCache.php @@ -0,0 +1,173 @@ +repository = $repository; + $this->daoFactory = $daoFactory; + } + + /** + * @param $roleId + * @return Role + * @throws InvalidArgumentException + */ + private function getRole($roleId) + { + if (isset($this->allRoles[$roleId])) { + return $this->allRoles[$roleId]; + } else { + throw new InvalidArgumentException('No such role #' . $roleId); + } + } + + /** + * @param $permissionId + * @return Permission + * @throws InvalidArgumentException + */ + private function getPermission($permissionId) + { + if (isset($this->allPermissions[$permissionId])) { + return $this->allPermissions[$permissionId]; + } else { + throw new InvalidArgumentException('No such permission #' . $permissionId); + } + } + + private function cacheData() + { + $dRole = $this->daoFactory->DRole(); + $dRoleHierarchy = $this->daoFactory->DRoleHierarchy(); + $dPermission = $this->daoFactory->DPermission(); + $dPermissionHierarchy = $this->daoFactory->DPermissionHierarchy(); + $dRolesPermissions = $this->daoFactory->DRolesPermissions(); + + if (!$this->isDataLoaded) { + $this->allRoles = $this->repository->select( + Select::select() + ->from($dRole) + ->map($dRole->_entityClass) + ->indexBy(0) + ); + + $data = $this->repository->select( + Select::select()->from($dRoleHierarchy) + ); + foreach ($data as $row) { + $this->allRoleHierarchy[$row['parentId']][] = $row['childId']; + } + + $this->allPermissions = $this->repository->select( + Select::select() + ->from($dPermission) + ->map($dPermission->_entityClass) + ->indexBy(0) + ); + + $data = $this->repository->select( + Select::select()->from($dPermissionHierarchy) + ); + foreach ($data as $row) { + $this->allPermissionsHierarchy[$row['parentId']][] = $row['childId']; + } + + $data = $this->repository->select( + Select::select()->from($dRolesPermissions) + ); + foreach ($data as $row) { + $this->allRolePermissions[$row['roleId']][] = $row['permissionId']; + } + + $this->isDataLoaded = true; + } + } + + /** + * @param Role|Role[] $roles + * @return Role[] + */ + public function expandRole($roles) + { + $roles = is_array($roles) ? $roles : array($roles); + + $this->cacheData(); + + $res = array(); + /* @var $role Role */ + foreach ((array)$roles as $role) { + $res[] = $role; + if (isset($this->allRoleHierarchy[$role->getId()])) { + foreach ($this->allRoleHierarchy[$role->getId()] as $childId) { + $res = array_merge($res, $this->expandRole($this->getRole($childId))); + } + } + } + + return $res; + } + + /** + * @param Permission|Permission[] $permissions + * @return Permission[] + */ + public function expandPermission($permissions) + { + $permissions = is_array($permissions) ? $permissions : array($permissions); + $this->cacheData(); + + $res = array(); + /* @var $permission Permission */ + foreach ((array)$permissions as $permission) { + $res[] = $permission; + if (isset($this->allPermissionsHierarchy[$permission->getId()])) { + foreach ($this->allPermissionsHierarchy[$permission->getId()] as $childId) { + $res = array_merge($res, $this->expandPermission($this->getPermission($childId))); + } + } + } + + return $res; + } + + /** + * Check role permission. + * + * Check only own permission, do not count role/permission hierarchy. + * + * @param Role $role + * @param Permission $permission + * @return bool + */ + public function hasPermission(Role $role, Permission $permission) + { + $this->cacheData(); + if (isset($this->allRolePermissions[$role->getId()])) { + return in_array($permission->getId(), $this->allRolePermissions[$role->getId()]); + } else { + return false; + } + } +} diff --git a/Rbac/Impl/Repository/RbacService.php b/Rbac/Impl/Repository/RbacService.php new file mode 100644 index 0000000..fb48dcf --- /dev/null +++ b/Rbac/Impl/Repository/RbacService.php @@ -0,0 +1,272 @@ +repository = $repository; + $this->cache = $cache; + + $this->daoFactory = $daoFactory; + } + + public function loadRole($roleId) + { + $dRole = $this->daoFactory->DRole(); + + return $this->repository->select( + Select::select() + ->from($dRole) + ->where($dRole->id->eq($roleId)) + ->map($dRole->_entityClass) + ->singleRow() + ); + } + + public function findRoles(RoleFilter $filter) + { + $select = Select::select($filter->role())->from($filter->role()); + + if ($filter->isUserInvolved()) { + $select->join($filter->user(), $filter->role()->_fkUser); + } + + if ($filter->isParentInvolved()) { + $toParent = $this->daoFactory->DRoleHierarchy('toParent'); + $select->join($toParent, $filter->role()->id->eq($toParent->childId)); + $select->join($filter->parent(), $toParent->parentId->eq($filter->parent()->id)); + } + + if ($filter->isChildInvolved()) { + $toChild = $this->daoFactory->DRoleHierarchy('toChild'); + $select->join($toChild, $filter->role()->id->eq($toChild->parentId)); + $select->join($filter->child(), $toChild->childId->eq($filter->child()->id)); + } + + return $this->repository->select( + $select + ->filter($filter) + ->map($filter->role()->_entityClass) + ); + } + + public function saveRole(Role $role) + { + $dRole = $this->daoFactory->DRole(); + + if ($this->loadRole($role->getId())) { + // nothing to do, there is one field already in database + } else { + $this->repository->insert( + Insert::insert() + ->into($dRole) + ->populate($role) + ); + } + } + + public function removeRole(Role $role) + { + $dRoleHierarchy = $this->daoFactory->DRoleHierarchy(); + $dRolesPermissions = $this->daoFactory->DRolesPermissions(); + $dUserRoles = $this->daoFactory->DUserRoles(); + $dRole = $this->daoFactory->DRole(); + + $this->repository->delete( + Delete::delete() + ->from($dRoleHierarchy) + ->where(Operator::orOp( + $dRoleHierarchy->childId->eq($role->getId()), + $dRoleHierarchy->parentId->eq($role->getId()) + )) + ); + + $this->repository->delete( + Delete::delete() + ->from($dRolesPermissions) + ->where($dRolesPermissions->roleId->eq($role->getId())) + ); + + $this->repository->delete( + Delete::delete() + ->from($dUserRoles) + ->where($dUserRoles->roleId->eq($role->getId())) + ); + + $this->repository->delete( + Delete::delete() + ->from($dRole) + ->where($dRole->id->eq($role->getId())) + ); + } + + public function findPermissions(PermissionFilter $filter) + { + $dPermission = $this->daoFactory->DPermission(); + + $select = Select::select($filter->permission())->from($filter->permission()); + + if ($filter->isRoleInvolved()) { + $select->join($filter->role(), $filter->permission()->_fkRole); + } + + $select->filter($filter)->map($dPermission->_entityClass); + return $this->repository->select($select); + } + + public function listRoles() + { + $dRole = $this->daoFactory->DRole(); + + return $this->repository->select( + Select::select($dRole->id) + ->from($dRole) + ->singleColumn() + ); + } + + public function listPermissions() + { + $dPermission = $this->daoFactory->DPermission(); + + return $this->repository->select( + Select::select($dPermission->id) + ->from($dPermission) + ->singleColumn() + ); + } + + /** + * @param ISubject $subject + * @param Role[] $roles + */ + public function setUserRoles(ISubject $subject, array $roles) + { + $dUserRoles = $this->daoFactory->DUserRoles(); + + $this->repository->delete( + Delete::delete() + ->from($dUserRoles) + ->where($dUserRoles->userId->eq($subject->getId())) + ); + + foreach ($roles as $role) { + $this->repository->insert( + Insert::insert() + ->into($dUserRoles) + ->set($dUserRoles->userId, $subject->getId()) + ->set($dUserRoles->roleId, $role->getId()) + ); + } + } + + /** + * @param Role $role + * @param Role[] $children + */ + public function setRoleChildren(Role $role, array $children) + { + $dRoleHierarchy = $this->daoFactory->DRoleHierarchy(); + + $this->repository->delete( + Delete::delete() + ->from($dRoleHierarchy) + ->where($dRoleHierarchy->parentId->eq($role->getId())) + ); + + foreach ($children as $child) { + $this->repository->insert( + Insert::insert() + ->into($dRoleHierarchy) + ->set($dRoleHierarchy->parentId, $role->getId()) + ->set($dRoleHierarchy->childId, $child->getId()) + ); + } + } + + /** + * @param Role $role + * @param Permission[] $permissions + */ + public function setRolePermission(Role $role, array $permissions) + { + $dRolesPermissions = $this->daoFactory->DRolesPermissions(); + + $this->repository->delete( + Delete::delete() + ->from($dRolesPermissions) + ->where($dRolesPermissions->roleId->eq($role->getId())) + ); + + foreach ($permissions as $permission) { + $this->repository->insert( + Insert::insert() + ->into($dRolesPermissions) + ->set($dRolesPermissions->roleId, $role->getId()) + ->set($dRolesPermissions->permissionId, $permission->getId()) + ); + } + } + + public function loadPermission($permissionId) + { + $dPermission = $this->daoFactory->DPermission(); + + return $this->repository->select( + Select::select() + ->from($dPermission) + ->where($dPermission->id->eq($permissionId)) + ->map($dPermission->_entityClass) + ->singleRow() + ); + } + + public function checkPermission(ISubject $subject, Permission $permission, array $params = array()) + { + $roleFilter = new RoleFilter( + $this->daoFactory->DRole(), + $this->daoFactory->DRole('parent'), + $this->daoFactory->DRole('child'), + $this->daoFactory->DUser() + ); + $roleFilter->where($roleFilter->user()->userId->eq($subject->getId())); + $roles = $this->cache->expandRole($this->findRoles($roleFilter)); + + $permissions = $this->cache->expandPermission($permission); + + foreach ($roles as $eachRole) { + foreach ($permissions as $eachPermission) { + if ($this->cache->hasPermission($eachRole, $eachPermission)) { + if ($eachPermission->check($subject, $params)) { + return true; + } + } + } + } + + return false; + } +} diff --git a/Rbac/Impl/Repository/RoleFilter.php b/Rbac/Impl/Repository/RoleFilter.php new file mode 100644 index 0000000..6d587e0 --- /dev/null +++ b/Rbac/Impl/Repository/RoleFilter.php @@ -0,0 +1,68 @@ +dRole = $dRole; + $this->dRoleParent = $parent; + $this->dRoleChild = $child; + $this->dUser = $dUser; + } + + public function role() + { + return $this->dRole; + } + + public function user() + { + $this->isUserInvolved = true; + return $this->dUser; + } + + /** + * @return DRbacRole + */ + public function child() + { + $this->isChildInvolved = true; + return $this->dRoleChild; + } + + /** + * @return DRbacRole + */ + public function parent() + { + $this->isParentInvolved = true; + return $this->dRoleParent; + } + + public function isUserInvolved() + { + return $this->isUserInvolved; + } + + public function isParentInvolved() + { + return $this->isParentInvolved; + } + + public function isChildInvolved() + { + return $this->isChildInvolved; + } +} diff --git a/Rbac/OwnerPermission.php b/Rbac/OwnerPermission.php new file mode 100644 index 0000000..ff0afe7 --- /dev/null +++ b/Rbac/OwnerPermission.php @@ -0,0 +1,10 @@ +getId() == $object->getOwnerId(); + } +} diff --git a/Rbac/Permission.php b/Rbac/Permission.php new file mode 100644 index 0000000..0678803 --- /dev/null +++ b/Rbac/Permission.php @@ -0,0 +1,45 @@ +children = $children; + } + + /** + * @return mixed + */ + public function getChildren() + { + return $this->children; + } + + /** + * @param mixed $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return mixed + */ + public function getId() + { + return $this->id; + } + + public function check(ISubject $subject, $params) + { + return true; + } +} diff --git a/Rbac/Role.php b/Rbac/Role.php new file mode 100644 index 0000000..555143a --- /dev/null +++ b/Rbac/Role.php @@ -0,0 +1,28 @@ +id = $id; + } + + /** + * @param mixed $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return mixed + */ + public function getId() + { + return $this->id; + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..aefc636 --- /dev/null +++ b/composer.json @@ -0,0 +1,22 @@ +{ + "name": "vda/security", + "type": "library", + "description": "User access control tools", + "keywords": ["rbac"], + "require": { + "php": ">=5.3.23", + "vda/datasource": "self.version", + "vda/query": "self.version", + "vda/util": "self.version" + }, + "autoload": { + "psr-0": { "": "" } + }, + "target-dir": "Vda/Security", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-trunk": "1-dev" + } + } +}