diff --git a/includes/YesWiki.php b/includes/YesWiki.php index 131c590c3..d55543679 100755 --- a/includes/YesWiki.php +++ b/includes/YesWiki.php @@ -949,7 +949,7 @@ public function UserIsOwner($tag = "") * @return string the ACL associated with the group $gname * @see UserIsInGroup to check if a user belongs to some group */ - public function GetGroupACL($group) + public function GetGroupACL($group) //FIXME { if (array_key_exists($group, $this->_groupsCache)) { return $this->_groupsCache[$group]; @@ -967,7 +967,7 @@ public function GetGroupACL($group) * The new acl for that group * @return boolean True if the new acl defines the group recursively */ - public function MakesGroupRecursive($gname, $acl, $origin = null, $checked = array()) + public function MakesGroupRecursive($gname, $acl, $origin = null, $checked = array()) //FIXME { $gname = strtolower(trim($gname)); if ($origin === null) { @@ -1014,7 +1014,7 @@ public function MakesGroupRecursive($gname, $acl, $origin = null, $checked = arr * 1001 if $gname is not named with alphanumeric chars * @see GetGroupACL */ - public function SetGroupACL($gname, $acl) + public function SetGroupACL($gname, $acl) //FIXME { if (preg_match('/[^A-Za-z0-9]/', $gname)) { return 1001; @@ -1049,7 +1049,7 @@ public function SetGroupACL($gname, $acl) * * @return array The list of all group names */ - public function GetGroupsList() + public function GetGroupsList() //FIXME { $res = $this->GetMatchingTriples(GROUP_PREFIX . '%', WIKINI_VOC_ACLS_URI); $prefix_len = strlen(GROUP_PREFIX); diff --git a/includes/controllers/ApiController.php b/includes/controllers/ApiController.php index dcd80fadc..e629bdf3d 100644 --- a/includes/controllers/ApiController.php +++ b/includes/controllers/ApiController.php @@ -16,6 +16,7 @@ use YesWiki\Core\Controller\CsrfTokenController; use YesWiki\Core\Controller\PageController; use YesWiki\Core\Controller\UserController; +use YesWiki\Core\Controller\GroupController; use YesWiki\Core\Exception\DeleteUserException; use YesWiki\Core\Exception\ExitException; use YesWiki\Core\Service\AclService; @@ -30,6 +31,12 @@ use YesWiki\Core\YesWikiController; use YesWiki\Security\Controller\SecurityController; +use YesWiki\Core\Exception\GroupNameDoesNotExistException; +use YesWiki\Core\Exception\GroupNameAlreadyUsedException; +use YesWiki\Core\Exception\UserNameDoesNotExistException; +use YesWiki\Core\Exception\InvalidGroupNameException; +use YesWiki\Core\Exception\UserEmailAlreadyUsedException; + class ApiController extends YesWikiController { /** @@ -248,6 +255,96 @@ public function getAllUsers($userFields = ['name', 'email', 'signuptime']) return new ApiResponse($users); } + + /** + * @Route("/api/groups",methods={"POST"},options={"acl":{"public"}}) + */ + public function createGroup() + { + $this->denyAccessUnlessAdmin(); + $groupController = $this->getService(GroupController::class); + + if (empty($_POST['name'])) { + $code = Response::HTTP_BAD_REQUEST; + $result = [ + 'error' => "\$_POST['name'] should not be empty", + ]; + } else { + try { + $group_name = $_POST['name']; + $users = empty($_POST['users']) ? array() : $_POST['users']; + $result = $groupController->create($group_name, $users); + $code = Response::HTTP_OK; + } catch (GroupNameAlreadyUsedException $th) { + $code = Response::HTTP_UNPROCESSABLE_ENTITY; + $result = [ + 'notCreated' => [strval($_POST['name'])], + 'error' => str_replace('{currentName}', strval($_POST('name')), _t('USERSETTINGS_NAME_ALREADY_USED')) + ]; + } catch (InvalidGroupNameException $th) { + $code = Response::HTTP_UNPROCESSABLE_ENTITY; + $result = [ + 'notCreated' => [strval($_POST['name'])], + 'error' => $th->getMessage() + ]; + } catch (UserNameDoesNotExistException | GroupNameDoesNotExistException $th) { + $code = Response::HTTP_UNPROCESSABLE_ENTITY; + $result = [ + 'notCreated' => [strval($_POST['name'])], + 'error' => str_replace('{currentName}', $th->getMessage(), _t('USERSETTINGS_NAME_NOT_FOUND')) + ]; + } catch (ExitException $th) { + throw $th; + } catch (Exception $th) { + $code = Response::HTTP_BAD_REQUEST; + $result = [ + 'notCreated' => [strval($_POST['name'])], + 'error' => $th->getMessage() + ]; + } catch (Throwable $th) { + $code = Response::HTTP_INTERNAL_SERVER_ERROR; + $result = [ + 'notCreated' => [strval($_POST['name'])], + 'error' => $th->getMessage() + ]; + } + } + return new ApiResponse($result, $code); + } + + /** + * @Route("/api/groups",methods={"GET"},options={"acl":{"public"}}) + */ + public function getAllGroups() + { + $this->denyAccessUnlessAdmin(); + $groupController = $this->getService(GroupController::class); + + return new ApiResponse($groupController->getAll()); + } + + + /** + * @Route("/api/groups/{group_name}",methods={"GET"}, options={"acl":{"public"}}) + */ + public function getGroup(string $group_name) + { + $this->denyAccessUnlessAdmin(); + $groupController = $this->getService(GroupController::class); + + try { + $result = $groupController->getMembers($group_name); + $code = Response::HTTP_OK; + } catch (GroupNameDoesNotExistException $th) { + $code = Response::HTTP_NOT_FOUND; + $result = [ + 'notFound' => $group_name, + 'error' => $th->getMessage() + ]; + } + return new ApiResponse($result, $code); + } + /** * @Route("/api/comments/{tag}",methods={"GET"}, options={"acl":{"public"}}) */ @@ -298,16 +395,6 @@ public function deleteCommentViaPostMethod($tag) return $this->deleteComment($tag); } - /** - * @Route("/api/groups", options={"acl":{"public"}}) - */ - public function getAllGroups() - { - $this->denyAccessUnlessAdmin(); - - return new ApiResponse($this->wiki->GetGroupsList()); - } - /** * @Route("/api/pages", options={"acl":{"public"}}) */ diff --git a/includes/controllers/GroupController.php b/includes/controllers/GroupController.php index c7f11c44c..3b44fad89 100644 --- a/includes/controllers/GroupController.php +++ b/includes/controllers/GroupController.php @@ -2,12 +2,12 @@ namespace YesWiki\Core\Controller; - use YesWiki\Core\Exception\InvalidGroupNameException; use YesWiki\Core\Exception\GroupNameDoesNotExistException; use YesWiki\Core\Exception\GroupNameAlreadyUsedException; use YesWiki\Core\Exception\UserNameDoesNotExistException; use YesWiki\Core\Exception\InvalidInputException; +use Exception; use YesWiki\Core\Service\GroupManager; use YesWiki\Core\Service\UserManager; use YesWiki\Core\YesWikiController; @@ -29,89 +29,145 @@ public function __construct( * @param string $groupName * @return bool */ - private function isNameValid(string $name):bool { - if ( str_starts_with($name, "@")) { + private function isNameValid(string $name): bool + { + if (str_starts_with($name, "@")) { $name = substr($name, 1); } - return preg_match('/[^A-Za-z0-9]/', $name); + return !preg_match('/[^A-Za-z0-9]/', $name); } - - + + /** * @param string $groupName * @return array the ACL associated with the current group */ - public function getMembers(string $groupName) : array + public function getMembers(string $groupName): array { + if ($this->groupManager->groupExists($groupName)) { return $this->groupManager->getMembers($groupName); + } + throw new GroupNameDoesNotExistException(_t('GROUP_NAME_DOES_NOT_EXIST')); } - - /** - * create group - * @param string $name group name - * @param ?array $users users and/or groups to add - * @throws GroupNameAlreadyExist - * @return void - */ - public function create(string $name, ?array $members): void + + /** + * create group + * @param string $name group name + * @param ?array $users users and/or groups to add + * @throws GroupNameAlreadyExist + * @return array|null + */ + public function create(string $name, ?array $members): array|null { if ($this->groupManager->groupExists($name)) { - throw new GroupNameAlreadyUsedException(_t('GROUP_NAME_ALREADY_USED')); - } - if ($this->isNameValid($name)) - { + throw new GroupNameAlreadyUsedException(_t('GROUP_NAME_ALREADY_USED')); + } + if ($this->isNameValid($name)) { + foreach($members as $member) { + switch ($this->checkMemberValidity($name, $member)) { + case 0: + break; + case 1: + throw new UserNameDoesNotExistException($member); + case 2: + throw new GroupNameDoesNotExistException(_t('GROUP_NAME_DOES_NOT_EXIST')); + case 3: + throw new InvalidInputException(_t('ERROR_RECURSIVE_GROUP')); + } + } $this->groupManager->create($name, $members); } else { - throw new InvalidGroupNameException(_t('INVALID_GROUP_NAME')); // FIXME vérifier INVALID_GROUP_NAME + throw new InvalidGroupNameException(_t('INVALID_GROUP_NAME')); + } + if ($this->groupManager->groupExists($name)) { + $entry = $this->groupManager->getMembers($name); + return array("name" => $name, "members" => $entry); } + throw new Exception(_t('ERROR_SAVING_GROUP') . '.'); + return null; } - - /** - * delete group - * @return void - */ + + public function getAll(): array + { + return $this->groupManager->getAll(); + } + + /** + * delete group + * @return void + */ public function delete(string $name): void { if ($this->groupManager->groupExists($name)) { $this->groupManager->delete($name); } } - - /** - * add users and/or groups to group - * @param string $group_name - * @param array $names users and/or groups to add - * @throws UserDoesNotExistException - * @throws GroupDoesNotExistException - * @return void - */ - public function add(string $group_name, array $names): void + + /** + * add users and/or groups to group + * @param string $group_name + * @param array $members users and/or groups to add + * @throws UserDoesNotExistException + * @throws GroupDoesNotExistException + * @throws InvalidInputException + * @return void + */ + public function add(string $group_name, array $members): void { if(!$this->groupManager->groupExists($group_name)) { - throw new GroupNameDoesNotExistException(); + throw new GroupNameDoesNotExistException(_t('GROUP_NAME_DOES_NOT_EXIST')); } - foreach ($names as $name) { - if(str_starts_with($name, "!")) { - $name = substr($name, 1); + foreach($members as $member) { + switch ($this->checkMemberValidity($group_name, $member)) { + case 0: + break; + case 1: + throw new UserNameDoesNotExistException(_t('USER_NAME_DOES_NOT_EXIST')); + case 2: + throw new GroupNameDoesNotExistException(_t('GROUP_NAME_DOES_NOT_EXIST')); + case 3: + throw new InvalidInputException(_t('ERROR_RECURSIVE_GROUP')); } - if(str_starts_with($name, "@")) { - - if(!$this->groupManager->groupExists($name)) { - throw new GroupNameDoesNotExistException(_t('GROUP_NAME_DOES_NOT_EXIST')); - } - if(!this->CheckGroupRecursive($name,$group_name)) { - throw new InvalidInputException(_t('RECURSIVE_GROUP_ERROR')); //FIXME - } - } else { - if(!$this->userManager->userExist($name)) { - throw new UserNameDoesNotExistException(_t('USER_NAME_DOES_NOT_EXIST')); - } + } + $this->groupManager->add($group_name, $members); + } + + /** + * Check if member is valid for group. Perform following check : + * - if $member is a user, check if the user exists + * - if $member is a group, check if the group exists + * - if $member is a group, check if the group doesn't define itself recursively + * @param string $group_name + * @param string $member + * @return : int + * - 0 if ok + * - 1 if user doesn't exist + * - 2 if group doesn't exist + * - 3 if group is recursive + */ + private function checkMemberValidity(string $group_name, string $member): int + { + if(str_starts_with($member, "!")) { + $$member = substr($member, 1); + } + if(str_starts_with($member, "@")) { + + if(!$this->groupManager->groupExists($member)) { + return 2; } + if(!this->CheckGroupRecursive($member, $group_name)) { + return 3; } - $this->groupManager->add($group_name, $name); + } else { + if(!$this->userManager->userExist($member)) { + return 1; + } + } + return 0; } - - /** + + + /** * Checks if a new group acl is not defined recursively * (this method expects that groups that are already defined are not themselves defined recursively...) * @@ -123,7 +179,7 @@ public function add(string $group_name, array $names): void private function CheckGroupRecursive($group_name, $origin, $checked = array()): bool { $group_name = strtolower(trim($group_name)); - if ($group_name === $origin) { + if ($group_name === $origin) { return true; } $members = this->get_members($group_name); @@ -153,7 +209,7 @@ private function CheckGroupRecursive($group_name, $origin, $checked = array()): $checked[] = $group_name; return false; } - + /** * remove users and/or groups from group * @param array $names users and/or groups to add @@ -163,18 +219,40 @@ private function CheckGroupRecursive($group_name, $origin, $checked = array()): */ public function remove(array $names): void { - $this->groupManager->removeMembers($names); + $this->groupManager->removeMembers($names); } - + /** * replace current members with new one - * @param string $groupName + * @param string $groupName * @param array $names new members List * @return bool * @throws UserDoesNotExistException * @throws GroupDoesNotExistException */ - public function update(string $groupName, array $names) { - + public function update(string $groupName, array $names) + { + if(!$this->groupManager->groupExists($groupName)) { + throw new GroupNameDoesNotExistException(_t('GROUP_NAME_DOES_NOT_EXIST')); + } + foreach ($names as $name) { + if(str_starts_with($name, "!")) { + $name = substr($name, 1); + } + if(str_starts_with($name, "@")) { + + if(!$this->groupManager->groupExists($name)) { + throw new GroupNameDoesNotExistException(_t('GROUP_NAME_DOES_NOT_EXIST')); + } + if(!this->CheckGroupRecursive($name, $groupName)) { + throw new InvalidInputException(_t('RECURSIVE_GROUP_ERROR')); //FIXME + } + } else { + if(!$this->userManager->userExist($name)) { + throw new UserNameDoesNotExistException(_t('USER_NAME_DOES_NOT_EXIST')); + } + } + } + $this->groupManager->update($groupName, $name); } } diff --git a/includes/exceptions/InvalidInputException.php b/includes/exceptions/InvalidInputException.php new file mode 100644 index 000000000..bb8af0959 --- /dev/null +++ b/includes/exceptions/InvalidInputException.php @@ -0,0 +1,9 @@ +tripleStore = $tripleStore; } - + /** * Check if group already exists * @param string $group_name @@ -23,91 +23,98 @@ public function __construct( */ public function groupExists(string $group_name): bool { - return $this->tripleStore->getOne($group_name, WIKINI_VOC_ACLS, GROUP_PREFIX) != null; + return $this->tripleStore->getMatching(GROUP_PREFIX . $group_name, WIKINI_VOC_ACLS_URI, null, '=') != null; } - + /** * create group with members * @param string $group_name - * @param array $members + * @param array $members * @return void */ - public function create(string $group_name, array $members): void { + public function create(string $group_name, array $members): void + { $member_str = implode("\n", $members); $this->tripleStore->create($group_name, WIKINI_VOC_ACLS, $member_str, GROUP_PREFIX); } - - public function delete(string $group_name): void { - $this->tripleStore->delete($group_name, WIKINI_VOC_ACLS, null, GROUP_PREFIX); + + public function delete(string $group_name): void + { + $this->tripleStore->delete($group_name, WIKINI_VOC_ACLS, null, GROUP_PREFIX); } - - + + /** * get list of all groups * @return string[] - * + * */ - public function getall(): array { + public function getall(): array + { $group_list = $this->tripleStore->getMatching(GROUP_PREFIX . '%', WIKINI_VOC_ACLS_URI); $prefix_len = strlen(GROUP_PREFIX); - return array_map(fn($value): string => substr($value['resource'], $prefix_len),$group_list); + return array_map(fn ($value): string => substr($value['resource'], $prefix_len), $group_list); } - + /** * get direct members of group. Do not list member of child groups. * @param string group_name * @return string[] */ - public function getMembers(string $group_name) :array { - $members = $this->tripleStore->getOne($group_name, WIKINI_VOC_ACLS, GROUP_PREFIX); - return explode("\n", $members); + public function getMembers(string $group_name): array + { + $members = $this->tripleStore->getOne($group_name, WIKINI_VOC_ACLS, GROUP_PREFIX); + return explode("\n", $members); } - + /** * @param string $group_name * @param array $members * @return void */ - public function addMembers(string $group_name, array $members):void { + public function addMembers(string $group_name, array $members): void + { $old_members = $this->getMembers($group_name); - $new_members = array_merge($old_members, $members); - $new_members = array_unique($$new_members); - $new_members = implode("\n", $new_members); - if($this->tripleStore->delete($group_name, WIKINI_VOC_ACLS, $old_members, GROUP_PREFIX)) { - $this->tripleStore->create($group_name, WIKINI_VOC_ACLS, $new_members, GROUP_PREFIX); - } else { - $this->tripleStore->update($group_name, WIKINI_VOC_ACLS, $old_members, $new_members , GROUP_PREFIX); - } + $new_members = array_merge($old_members, $members); + $new_members = array_unique($$new_members); + $new_members = implode("\n", $new_members); + if($this->tripleStore->delete($group_name, WIKINI_VOC_ACLS, $old_members, GROUP_PREFIX)) { + $this->tripleStore->create($group_name, WIKINI_VOC_ACLS, $new_members, GROUP_PREFIX); + } else { + $this->tripleStore->update($group_name, WIKINI_VOC_ACLS, $old_members, $new_members, GROUP_PREFIX); + } } - - + + /** * @param string $group_name * @param array $members * @return void */ - public function removeMembers(string $group_name, array $members): void { - $old_members = $this->getMembers($group_name); - $new_members = array_diff($$old_members, $members); - $new_members = implode("\n", $new_members); - if($this->tripleStore->delete($group_name, WIKINI_VOC_ACLS, $old_members, GROUP_PREFIX)) { - $this->tripleStore->create($group_name, WIKINI_VOC_ACLS, $new_members, GROUP_PREFIX); - } else { - $this->tripleStore->update($group_name, WIKINI_VOC_ACLS, $old_members, $new_members , GROUP_PREFIX); - } + public function removeMembers(string $group_name, array $members): void + { + $old_members = $this->getMembers($group_name); + $new_members = array_diff($$old_members, $members); + $new_members = implode("\n", $new_members); + if($this->tripleStore->delete($group_name, WIKINI_VOC_ACLS, $old_members, GROUP_PREFIX)) { + $this->tripleStore->create($group_name, WIKINI_VOC_ACLS, $new_members, GROUP_PREFIX); + } else { + $this->tripleStore->update($group_name, WIKINI_VOC_ACLS, $old_members, $new_members, GROUP_PREFIX); + } } - + /** * @param string $group_name * @param array $members * @return void */ - public function updateMembers(string $group_name, array $members): void { + public function updateMembers(string $group_name, array $members): void + { if($this->tripleStore->delete($group_name, WIKINI_VOC_ACLS, null, GROUP_PREFIX)) { - $this->tripleStore->create($group_name, WIKINI_VOC_ACLS, $members, GROUP_PREFIX); - } else { - $this->tripleStore->update($group_name, WIKINI_VOC_ACLS, null, $members , GROUP_PREFIX); - } + $this->tripleStore->create($group_name, WIKINI_VOC_ACLS, $members, GROUP_PREFIX); + } else { + $this->tripleStore->update($group_name, WIKINI_VOC_ACLS, null, $members, GROUP_PREFIX); + } } } diff --git a/includes/services/UserManager.php b/includes/services/UserManager.php index d1f7e255e..19db0b7e1 100644 --- a/includes/services/UserManager.php +++ b/includes/services/UserManager.php @@ -64,8 +64,9 @@ private function arrayToUser(?array $userAsArray = null, bool $fillEmpty = false // be carefull the User::__construct is really strict about list of properties that should set return new User($userAsArray); } - - public function userExist($name): bool { + + public function userExist($name): bool + { return !empty($this->getOneByName($name)); } diff --git a/tools/login/lang/login_en.inc.php b/tools/login/lang/login_en.inc.php index 132532ff9..65d195897 100644 --- a/tools/login/lang/login_en.inc.php +++ b/tools/login/lang/login_en.inc.php @@ -49,4 +49,5 @@ 'USERSETTINGS_SIGNUP_MISSING_INPUT' => 'The \'{parameters}\' parameters cannot be empty!', 'USERSETTINGS_NAME_ALREADY_USED' => 'The identifier "{currentName}" already exists!', 'USERSETTINGS_EMAIL_ALREADY_USED' => 'The email "{email}" is already used by another account!', + 'USERSETTINGS_NAME_NOT_FOUND' => 'The identifier "{currentName}" does not exist!', ]; diff --git a/tools/login/lang/login_fr.inc.php b/tools/login/lang/login_fr.inc.php index e6eef3221..e0a3a616e 100755 --- a/tools/login/lang/login_fr.inc.php +++ b/tools/login/lang/login_fr.inc.php @@ -57,4 +57,5 @@ 'USERSETTINGS_CHANGE_PWD_IN_IFRAME' => "Vous vous apprêtez à changer votre mot de passe dans une fenêtre de type iframe.\n" . "Pour éviter les attaques par enregistrement de vos touches, assurez-vous que l'url du site commence bien par {baseUrl}.\n" . "Au moindre doute, ouvrez ce formulaire dans une page dédiée en cliquant sur ce lien {link}.", + 'USERSETTINGS_NAME_NOT_FOUND' => 'L\'identifiant "{currentName}" n\'existe pas !', ];