From 37c3c704f3b887e64e2511cb29ef43b3eaf13ab7 Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Fri, 25 Jun 2021 11:05:23 -0100 Subject: [PATCH] Allow an admin account to manage Circles using ocs Signed-off-by: Maxence Lange --- appinfo/routes.php | 42 ++- lib/Controller/AdminController.php | 499 +++++++++++++++++++++++++++ lib/Service/FederatedUserService.php | 45 +++ 3 files changed, 585 insertions(+), 1 deletion(-) create mode 100644 lib/Controller/AdminController.php diff --git a/appinfo/routes.php b/appinfo/routes.php index b73e13063..8d4a3d3db 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -31,6 +31,8 @@ return [ 'ocs' => [ + + // LocalController ['name' => 'Local#circles', 'url' => '/circles', 'verb' => 'GET'], ['name' => 'Local#create', 'url' => '/circles', 'verb' => 'POST'], ['name' => 'Local#destroy', 'url' => '/circles/{circleId}', 'verb' => 'DELETE'], @@ -58,7 +60,45 @@ ['name' => 'Local#editDescription', 'url' => '/circles/{circleId}/description', 'verb' => 'PUT'], ['name' => 'Local#editSettings', 'url' => '/circles/{circleId}/settings', 'verb' => 'PUT'], ['name' => 'Local#editConfig', 'url' => '/circles/{circleId}/config', 'verb' => 'PUT'], - ['name' => 'Local#link', 'url' => '/link/{circleId}/{singleId}', 'verb' => 'GET'] + ['name' => 'Local#link', 'url' => '/link/{circleId}/{singleId}', 'verb' => 'GET'], + + // AdminController + ['name' => 'Admin#circles', 'url' => '/admin/{emulated}/circles', 'verb' => 'GET'], + ['name' => 'Admin#create', 'url' => '/admin/{emulated}/circles', 'verb' => 'POST'], + ['name' => 'Admin#destroy', 'url' => '/admin/{emulated}/circles/{circleId}', 'verb' => 'DELETE'], + [ + 'name' => 'Admin#memberAdd', 'url' => '/admin/{emulated}/circles/{circleId}/members', + 'verb' => 'POST' + ], + [ + 'name' => 'Admin#memberLevel', + 'url' => '/admin/{emulated}/circles/{circleId}/members/{memberId}/level', + 'verb' => 'PUT' + ], + + ['name' => 'Admin#circleDetails', 'url' => '/admin/{emulated}/circles/{circleId}', 'verb' => 'GET'], + ['name' => 'Admin#members', 'url' => '/admin/{emulated}/circles/{circleId}/members', 'verb' => 'GET'], + ['name' => 'Admin#memberAdd', 'url' => '/admin/{emulated}/circles/{circleId}/members', 'verb' => 'POST'], + [ + 'name' => 'Admin#memberConfirm', 'url' => '/admin/{emulated}/circles/{circleId}/members/{memberId}', + 'verb' => 'PUT' + ], + [ + 'name' => 'Admin#memberRemove', 'url' => '/admin/{emulated}/circles/{circleId}/members/{memberId}', + 'verb' => 'DELETE' + ], + [ + 'name' => 'Admin#memberLevel', 'url' => '/admin/{emulated}/circles/{circleId}/members/{memberId}/level', + 'verb' => 'PUT' + ], + ['name' => 'Admin#circleJoin', 'url' => '/admin/{emulated}/circles/{circleId}/join', 'verb' => 'PUT'], + ['name' => 'Admin#circleLeave', 'url' => '/admin/{emulated}/circles/{circleId}/leave', 'verb' => 'PUT'], + ['name' => 'Admin#editName', 'url' => '/admin/{emulated}/circles/{circleId}/name', 'verb' => 'PUT'], + ['name' => 'Admin#editDescription', 'url' => '/admin/{emulated}/circles/{circleId}/description', 'verb' => 'PUT'], + ['name' => 'Admin#editSettings', 'url' => '/admin/{emulated}/circles/{circleId}/settings', 'verb' => 'PUT'], + ['name' => 'Admin#editConfig', 'url' => '/admin/{emulated}/circles/{circleId}/config', 'verb' => 'PUT'], + ['name' => 'Admin#link', 'url' => '/admin/{emulated}/link/{circleId}/{singleId}', 'verb' => 'GET'], + ], 'routes' => [ diff --git a/lib/Controller/AdminController.php b/lib/Controller/AdminController.php new file mode 100644 index 000000000..c133b9a6c --- /dev/null +++ b/lib/Controller/AdminController.php @@ -0,0 +1,499 @@ + + * @copyright 2021 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +namespace OCA\Circles\Controller; + + +use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Deserialize; +use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger; +use Exception; +use OCA\Circles\Exceptions\ContactAddressBookNotFoundException; +use OCA\Circles\Exceptions\ContactFormatException; +use OCA\Circles\Exceptions\ContactNotFoundException; +use OCA\Circles\Exceptions\FederatedUserException; +use OCA\Circles\Exceptions\FederatedUserNotFoundException; +use OCA\Circles\Exceptions\InvalidIdException; +use OCA\Circles\Exceptions\RequestBuilderException; +use OCA\Circles\Exceptions\SingleCircleNotFoundException; +use OCA\Circles\Model\FederatedUser; +use OCA\Circles\Model\Member; +use OCA\Circles\Service\CircleService; +use OCA\Circles\Service\ConfigService; +use OCA\Circles\Service\FederatedUserService; +use OCA\Circles\Service\MemberService; +use OCA\Circles\Service\MembershipService; +use OCA\Circles\Service\SearchService; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSException; +use OCP\AppFramework\OCSController; +use OCP\IRequest; +use OCP\IUserSession; + + +/** + * Class AdminController + * + * @package OCA\Circles\Controller + */ +class AdminController extends OcsController { + + + use TNC22Deserialize; + use TNC22Logger; + + + /** @var IUserSession */ + private $userSession; + + /** @var FederatedUserService */ + private $federatedUserService; + + /** @var CircleService */ + private $circleService; + + /** @var MemberService */ + private $memberService; + + /** @var MembershipService */ + private $membershipService; + + /** @var SearchService */ + private $searchService; + + /** @var ConfigService */ + protected $configService; + + + /** + * LocalController constructor. + * + * @param string $appName + * @param IRequest $request + * @param IUserSession $userSession + * @param FederatedUserService $federatedUserService + * @param CircleService $circleService + * @param MemberService $memberService + * @param MembershipService $membershipService + * @param SearchService $searchService + * @param ConfigService $configService + */ + public function __construct( + string $appName, + IRequest $request, + IUserSession $userSession, + FederatedUserService $federatedUserService, + CircleService $circleService, + MemberService $memberService, + MembershipService $membershipService, + SearchService $searchService, + ConfigService $configService + ) { + parent::__construct($appName, $request); + $this->userSession = $userSession; + $this->federatedUserService = $federatedUserService; + $this->circleService = $circleService; + $this->memberService = $memberService; + $this->membershipService = $membershipService; + $this->searchService = $searchService; + $this->configService = $configService; + + $this->setup('app', 'circles'); + } + + + /** + * @param string $emulated + * @param string $name + * @param bool $personal + * @param bool $local + * + * @return DataResponse + * @throws OCSException + */ + public function create( + string $emulated, + string $name, + bool $personal = false, + bool $local = false + ): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + $circle = $this->circleService->create($name, null, $personal, $local); + + return new DataResponse($this->serializeArray($circle)); + } catch (Exception $e) { + throw new OcsException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * + * @return DataResponse + * @throws OCSException + */ + public function destroy(string $emulated, string $circleId): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + $circle = $this->circleService->destroy($circleId); + + return new DataResponse($this->serializeArray($circle)); + } catch (Exception $e) { + throw new OcsException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * @param string $userId + * @param int $type + * + * @return DataResponse + * @throws OCSException + */ + public function memberAdd(string $emulated, string $circleId, string $userId, int $type): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + + // exception in Contact + if ($type === Member::TYPE_CONTACT) { + $currentUser = $this->federatedUserService->getCurrentUser(); + if (!$this->configService->isLocalInstance($currentUser->getInstance())) { + throw new OCSException('works only from local instance', 404); + } + + $userId = $currentUser->getUserId() . '/' . $userId; + } + + $federatedUser = $this->federatedUserService->generateFederatedUser($userId, $type); + $result = $this->memberService->addMember($circleId, $federatedUser); + + return new DataResponse($this->serializeArray($result)); + } catch (Exception $e) { + throw new OCSException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * @param string $memberId + * @param string|int $level + * + * @return DataResponse + * @throws OCSException + */ + public function memberLevel(string $emulated, string $circleId, string $memberId, $level): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + if (is_int($level)) { + $level = Member::parseLevelInt($level); + } else { + $level = Member::parseLevelString($level); + } + + $this->memberService->getMemberById($memberId, $circleId); + $result = $this->memberService->memberLevel($memberId, $level); + + return new DataResponse($this->serializeArray($result)); + } catch (Exception $e) { + throw new OcsException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * + * @return DataResponse + * @throws OCSException + */ + public function circles(string $emulated): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + + return new DataResponse($this->serializeArray($this->circleService->getCircles())); + } catch (Exception $e) { + throw new OCSException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * + * @return DataResponse + * @throws OCSException + */ + public function circleDetails(string $emulated, string $circleId): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + + return new DataResponse($this->serialize($this->circleService->getCircle($circleId))); + } catch (Exception $e) { + throw new OcsException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * + * @return DataResponse + * @throws OCSException + */ + public function circleJoin(string $emulated, string $circleId): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + $result = $this->circleService->circleJoin($circleId); + + return new DataResponse($this->serializeArray($result)); + } catch (Exception $e) { + throw new OCSException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * + * @return DataResponse + * @throws OCSException + */ + public function circleLeave(string $emulated, string $circleId): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + $result = $this->circleService->circleLeave($circleId); + + return new DataResponse($this->serializeArray($result)); + } catch (Exception $e) { + throw new OCSException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * @param string $memberId + * + * @return DataResponse + * @throws OCSException + */ + public function memberConfirm(string $emulated, string $circleId, string $memberId): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + + $member = $this->memberService->getMemberById($memberId, $circleId); + $federatedUser = new FederatedUser(); + $federatedUser->importFromIFederatedUser($member); + + $result = $this->memberService->addMember($circleId, $federatedUser); + + return new DataResponse($this->serializeArray($result)); + } catch (Exception $e) { + throw new OCSException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * @param string $memberId + * + * @return DataResponse + * @throws OCSException + */ + public function memberRemove(string $emulated, string $circleId, string $memberId): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + $this->memberService->getMemberById($memberId, $circleId); + + $result = $this->memberService->removeMember($memberId); + + return new DataResponse($this->serializeArray($result)); + } catch (Exception $e) { + throw new OCSException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * + * @return DataResponse + * @throws OCSException + */ + public function members(string $emulated, string $circleId): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + + return new DataResponse($this->serializeArray($this->memberService->getMembers($circleId))); + } catch (Exception $e) { + throw new OCSException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * @param string $value + * + * @return DataResponse + * @throws OCSException + */ + public function editName(string $emulated, string $circleId, string $value): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + + $outcome = $this->circleService->updateName($circleId, $value); + + return new DataResponse($this->serializeArray($outcome)); + } catch (Exception $e) { + throw new OCSException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * @param string $value + * + * @return DataResponse + * @throws OCSException + */ + public function editDescription(string $emulated, string $circleId, string $value): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + + $outcome = $this->circleService->updateDescription($circleId, $value); + + return new DataResponse($this->serializeArray($outcome)); + } catch (Exception $e) { + throw new OCSException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * @param array $value + * + * @return DataResponse + * @throws OCSException + */ + public function editSettings(string $emulated, string $circleId, array $value): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + + $outcome = $this->circleService->updateSettings($circleId, $value); + + return new DataResponse($this->serializeArray($outcome)); + } catch (Exception $e) { + throw new OCSException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * @param int $value + * + * @return DataResponse + * @throws OCSException + */ + public function editConfig(string $emulated, string $circleId, int $value): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + + $outcome = $this->circleService->updateConfig($circleId, $value); + + return new DataResponse($this->serializeArray($outcome)); + } catch (Exception $e) { + throw new OCSException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * @param string $circleId + * @param string $singleId + * + * @return DataResponse + * @throws OCSException + */ + public function link(string $emulated, string $circleId, string $singleId): DataResponse { + try { + $this->setLocalFederatedUser($emulated); + $membership = $this->membershipService->getMembership($circleId, $singleId); + + return new DataResponse($this->serialize($membership)); + } catch (Exception $e) { + throw new OCSException($e->getMessage(), $e->getCode()); + } + } + + + /** + * @param string $emulated + * + * @throws FederatedUserException + * @throws FederatedUserNotFoundException + * @throws InvalidIdException + * @throws RequestBuilderException + * @throws SingleCircleNotFoundException + * @throws ContactAddressBookNotFoundException + * @throws ContactFormatException + * @throws ContactNotFoundException + */ + private function setLocalFederatedUser(string $emulated): void { + $user = $this->userSession->getUser(); + $this->federatedUserService->setCurrentPatron($user->getUID()); + $this->federatedUserService->setLocalCurrentUserId($emulated); + } + +} + diff --git a/lib/Service/FederatedUserService.php b/lib/Service/FederatedUserService.php index 7350cde51..7206fcd8d 100644 --- a/lib/Service/FederatedUserService.php +++ b/lib/Service/FederatedUserService.php @@ -147,6 +147,9 @@ class FederatedUserService { /** @var bool */ private $initiatedByOcc = false; + /** @var FederatedUser */ + private $initiatedByAdmin = null; + /** * FederatedUserService constructor. @@ -332,6 +335,46 @@ public function isInitiatedByOcc(): bool { return $this->initiatedByOcc; } + /** + * @return bool + */ + public function isInitiatedByAdmin(): bool { + return !is_null($this->initiatedByAdmin); + } + + /** + * @param FederatedUser $patron + */ + public function setInitiatedByAdmin(FederatedUser $patron): void { + $this->initiatedByAdmin = $patron; + } + + /** + * @return FederatedUser + */ + public function getInitiatedByAdmin(): FederatedUser { + return $this->initiatedByAdmin; + } + + /** + * @param IUser $user + * + * @throws ContactAddressBookNotFoundException + * @throws ContactFormatException + * @throws ContactNotFoundException + * @throws FederatedUserException + * @throws FederatedUserNotFoundException + * @throws InvalidIdException + * @throws RequestBuilderException + * @throws SingleCircleNotFoundException + */ + public function setCurrentPatron(string $userId): void { + $patron = $this->getLocalFederatedUser($userId, false); + + $this->setInitiatedByAdmin($patron); + } + + /** * @param Member $member * @@ -346,6 +389,8 @@ public function isInitiatedByOcc(): bool { public function setMemberPatron(Member $member): void { if ($this->isInitiatedByOcc()) { $member->setInvitedBy($this->getAppInitiator('occ', Member::APP_OCC)); + } else if ($this->isInitiatedByAdmin()) { + $member->setInvitedBy($this->getInitiatedByAdmin()); } else { $member->setInvitedBy($this->getCurrentUser()); }