-
Notifications
You must be signed in to change notification settings - Fork 67
feat: Add the possibility to create invitation links to circles #2254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -593,6 +593,97 @@ public function link(string $circleId, string $singleId): DataResponse { | |
| } | ||
| } | ||
|
|
||
| #[NoAdminRequired] | ||
| public function createInvitation(string $circleId): DataResponse { | ||
| try { | ||
| $this->setCurrentFederatedUser(); | ||
|
|
||
| $outcome = $this->circleService->createInvitation($circleId); | ||
|
|
||
| return new DataResponse($this->serializeArray($outcome)); | ||
| } catch (\Exception $e) { | ||
| $this->e($e, ['circleId' => $circleId]); | ||
| throw new OCSException($e->getMessage(), (int)$e->getCode(), $e); | ||
| } | ||
| } | ||
|
|
||
| #[NoAdminRequired] | ||
| public function revokeInvitation(string $circleId): DataResponse { | ||
| try { | ||
| $this->setCurrentFederatedUser(); | ||
|
|
||
| $outcome = $this->circleService->revokeInvitation($circleId); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as createInvitation() |
||
|
|
||
| return new DataResponse($this->serializeArray($outcome)); | ||
| } catch (\Exception $e) { | ||
| $this->e($e, ['circleId' => $circleId]); | ||
| throw new OCSException($e->getMessage(), (int)$e->getCode(), $e); | ||
| } | ||
| } | ||
|
|
||
| #[NoAdminRequired] | ||
| #[BruteForceProtection(action: 'getInvitation')] | ||
| public function getInvitation(string $invitationCode): DataResponse { | ||
| try { | ||
| $this->setCurrentFederatedUser(); | ||
|
|
||
| $circleProbe = (new CircleProbe()) | ||
| ->includeSystemCircles() | ||
| ->includeHiddenCircles() | ||
| ->filterByInvitationCode($invitationCode); | ||
|
|
||
| $circles = $this->circleService->getCircles($circleProbe); | ||
| if (empty($circles)) { | ||
| return new DataResponse([], Http::STATUS_NOT_FOUND); | ||
| } | ||
| $circle = reset($circles); | ||
|
|
||
| $membershipStatus = 'NOT_A_MEMBER'; | ||
| if ($circle->hasInitiator()) { | ||
| if ($circle->getInitiator()->getLevel() > Member::LEVEL_NONE) { | ||
| $membershipStatus = 'MEMBER'; | ||
| } elseif ($circle->getInitiator()->getStatus() === Member::STATUS_REQUEST) { | ||
| $membershipStatus = 'REQUESTED_MEMBERSHIP'; | ||
| } | ||
| } | ||
|
|
||
| return new DataResponse([ | ||
| 'circleId' => $circle->getSingleId(), | ||
| 'circleName' => $circle->getName(), | ||
| 'membershipStatus' => $membershipStatus, | ||
| ]); | ||
| } catch (\Exception $e) { | ||
| $this->e($e, ['circleId' => $invitationCode]); | ||
| throw new OCSException($e->getMessage(), (int)$e->getCode(), $e); | ||
| } | ||
| } | ||
|
|
||
| #[NoAdminRequired] | ||
| #[BruteForceProtection(action: 'joinInvitation')] | ||
| public function joinInvitation(string $invitationCode): DataResponse { | ||
| try { | ||
| $this->setCurrentFederatedUser(); | ||
|
|
||
| $circleProbe = (new CircleProbe()) | ||
| ->includeSystemCircles() | ||
| ->includeHiddenCircles() | ||
| ->filterByInvitationCode($invitationCode); | ||
|
|
||
| $circles = $this->circleService->getCircles($circleProbe); | ||
| if (empty($circles)) { | ||
| return new DataResponse([], Http::STATUS_NOT_FOUND); | ||
| } | ||
| $circle = reset($circles); | ||
|
|
||
| $result = $this->circleService->circleJoin($circle->getSingleId(), $invitationCode); | ||
|
|
||
| return new DataResponse($this->serializeArray($result)); | ||
| } catch (\Exception $e) { | ||
| $this->e($e, ['circleId' => $invitationCode]); | ||
| throw new OCSException($e->getMessage(), (int)$e->getCode(), $e); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @throws FederatedUserException | ||
| * @throws FederatedUserNotFoundException | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /** | ||
| * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||
| */ | ||
|
|
||
| namespace OCA\Circles\Db; | ||
|
|
||
| use OCA\Circles\Exceptions\InvalidIdException; | ||
| use OCA\Circles\Model\CircleInvitation; | ||
|
|
||
| /** | ||
| * Class CircleInvitationRequest | ||
| * | ||
| * @package OCA\Circles\Db | ||
| */ | ||
| class CircleInvitationRequest extends CircleRequestBuilder { | ||
| /** | ||
| * @throws InvalidIdException | ||
| */ | ||
| public function save(CircleInvitation $circleInvitation): void { | ||
| $this->confirmValidId($circleInvitation->getCircleId()); | ||
|
|
||
| $qb = $this->getQueryBuilder(); | ||
| $qb->insert(self::TABLE_INVITATIONS) | ||
| ->setValue('created', $qb->createNamedParameter($this->timezoneService->getUTCDate())); | ||
| $qb->setValue('circle_id', $qb->createNamedParameter($circleInvitation->getCircleId())) | ||
| ->setValue('invitation_code', $qb->createNamedParameter($circleInvitation->getInvitationCode())) | ||
| ->setValue('created_by', $qb->createNamedParameter($circleInvitation->getCreatedBy())); | ||
| $qb->executeStatement(); | ||
| } | ||
|
|
||
| /** | ||
| * @throws InvalidIdException | ||
| */ | ||
| public function replace(CircleInvitation $circleInvitation): void { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could there be multiple invitation per circle? If so, shouldn't we delete a specific invitation, and not all of them?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not now. In theory we can add that, but in this case we need rework UI and introduce something like "invitation link name" to distinguish links. I would like to start with something really simple and improve it later |
||
| $qb = $this->getQueryBuilder(); | ||
| $qb->delete(self::TABLE_INVITATIONS); | ||
| $qb->limitToCircleId($circleInvitation->getCircleId()); | ||
| $qb->executeStatement(); | ||
|
|
||
| $this->save($circleInvitation); | ||
| } | ||
|
|
||
| public function delete(string $circleId): void { | ||
| $qb = $this->getQueryBuilder(); | ||
| $qb->delete(self::TABLE_INVITATIONS); | ||
| $qb->limitToCircleId($circleId); | ||
| $qb->executeStatement(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -167,13 +167,17 @@ public function getCircles(?IFederatedUser $initiator, CircleProbe $probe): arra | |
| $qb->limitToInitiator(CoreQueryBuilder::CIRCLE, $initiator); | ||
| $qb->orderBy($qb->generateAlias(CoreQueryBuilder::CIRCLE, CoreQueryBuilder::INITIATOR) . '.level', 'desc'); | ||
| $qb->addOrderBy(CoreQueryBuilder::CIRCLE . '.display_name', 'asc'); | ||
| $qb->leftJoinCircleInvitation(CoreQueryBuilder::CIRCLE); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we join only if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
| if ($probe->hasFilterMember()) { | ||
| $qb->limitToDirectMembership(CoreQueryBuilder::CIRCLE, $probe->getFilterMember()); | ||
| } | ||
| if ($probe->hasFilterCircle()) { | ||
| $qb->filterCircleDetails($probe->getFilterCircle()); | ||
| } | ||
| if ($probe->hasInvitationCode()) { | ||
| $qb->filterInvitationCode(CoreQueryBuilder::CIRCLE, $probe->getInvitationCode()); | ||
| } | ||
| if ($probe->hasFilterRemoteInstance()) { | ||
| $qb->limitToRemoteInstance(CoreQueryBuilder::CIRCLE, $probe->getFilterRemoteInstance(), false); | ||
| } | ||
|
|
@@ -375,6 +379,7 @@ public function getCircle( | |
| $qb->limitToUniqueId($id); | ||
| $qb->filterCircles(CoreQueryBuilder::CIRCLE, $probe); | ||
| $qb->leftJoinOwner(CoreQueryBuilder::CIRCLE); | ||
| $qb->leftJoinCircleInvitation(CoreQueryBuilder::CIRCLE); | ||
| // $qb->setOptions( | ||
| // [CoreRequestBuilder::CIRCLE, CoreRequestBuilder::INITIATOR], [ | ||
| // 'mustBeMember' => false, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
|
|
||
| /** | ||
| * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||
| */ | ||
|
|
||
| namespace OCA\Circles\Exceptions; | ||
|
|
||
| class CircleInvitationNotFoundException extends FederatedItemNotFoundException { | ||
| } |

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this only be reachable by members with certain levels (e.g. admin, owner)? as it is right now, seems like anyone can create invitations
you can see on other methods of this controller how permissions are handled, for example here:
https://github.com/nextcloud/circles/blob/master/lib/Controller/LocalController.php#L423