diff --git a/lib/AppInfo/Capabilities.php b/lib/AppInfo/Capabilities.php index 14c3e1d06..608b7de2c 100644 --- a/lib/AppInfo/Capabilities.php +++ b/lib/AppInfo/Capabilities.php @@ -180,7 +180,8 @@ private function getCapabilitiesCircleConstants(): array { Member::TYPE_APP => $this->l10n->t('Nextcloud App') ], 'extra' => [ - Member::APP_CIRCLES => 'Circles App' + Member::APP_CIRCLES => 'Circles App', + Member::APP_OCC => 'occ Command Line' ] ] ]; diff --git a/lib/Command/MembersList.php b/lib/Command/MembersList.php index c51a5032d..9fd38d127 100644 --- a/lib/Command/MembersList.php +++ b/lib/Command/MembersList.php @@ -230,8 +230,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $table = new Table($output); $table->setHeaders( [ - 'Circle Id', 'Circle Name', 'Member Id', 'Single Id', 'Type', 'Source', 'Username', - 'Instance', 'Level' + 'Circle Id', 'Circle Name', 'Member Id', 'Single Id', 'Type', 'Source', 'Invited By', + 'Username', 'Instance', 'Level' ] ); $table->render(); @@ -251,6 +251,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $member->getSingleId(), Member::$TYPE[$member->getUserType()], $member->hasBasedOn() ? Circle::$DEF_SOURCE[$member->getBasedOn()->getSource()] : '', + ($member->hasInvitedBy()) ? $member->getInvitedBy()->getUserId() + . $this->configService->displayInstance( + $member->getInvitedBy()->getInstance(), true + ) : 'Unknown', ($this->input->getOption('display-name')) ? $member->getBasedOn()->getDisplayName() : $member->getUserId(), $this->configService->displayInstance($member->getInstance()), diff --git a/lib/Db/CoreQueryBuilder.php b/lib/Db/CoreQueryBuilder.php index 85d91cd25..e0a3231c2 100644 --- a/lib/Db/CoreQueryBuilder.php +++ b/lib/Db/CoreQueryBuilder.php @@ -71,6 +71,7 @@ class CoreQueryBuilder extends NC22ExtendedQueryBuilder { const UPSTREAM_MEMBERSHIPS = 'upstreammemberships'; const INHERITANCE_FROM = 'inheritancefrom'; const INHERITED_BY = 'inheritedby'; + const INVITED_BY = 'invitedby'; const MOUNT = 'mount'; const MOUNTPOINT = 'mountpoint'; const SHARE = 'share'; @@ -109,7 +110,7 @@ class CoreQueryBuilder extends NC22ExtendedQueryBuilder { self::MEMBER => [ self::MEMBERSHIPS, self::INHERITANCE_FROM, - self::CIRCLE => [ + self::CIRCLE => [ self::OPTIONS => [ 'getData' => true ], @@ -123,10 +124,14 @@ class CoreQueryBuilder extends NC22ExtendedQueryBuilder { self::BASED_ON, self::INHERITED_BY => [ self::MEMBERSHIPS + ], + self::INVITED_BY => [ + self::OWNER, + self::BASED_ON ] ] ], - self::BASED_ON => [ + self::BASED_ON => [ self::OWNER, self::MEMBERSHIPS, self::INITIATOR => [ @@ -136,11 +141,15 @@ class CoreQueryBuilder extends NC22ExtendedQueryBuilder { ] ] ], - self::REMOTE => [ + self::REMOTE => [ self::MEMBER, self::CIRCLE => [ self::OWNER ] + ], + self::INVITED_BY => [ + self::OWNER, + self::BASED_ON ] ], self::SHARE => [ @@ -690,6 +699,33 @@ public function leftJoinCircle( } + /** + * @param string $aliasMember + * + * @throws RequestBuilderException + */ + public function leftJoinInvitedBy(string $aliasMember): void { + if ($this->getType() !== QueryBuilder::SELECT) { + return; + } + + try { + $aliasInvitedBy = $this->generateAlias($aliasMember, self::INVITED_BY); + } catch (RequestBuilderException $e) { + return; + } + + $expr = $this->expr(); + $this->generateCircleSelectAlias($aliasInvitedBy) + ->leftJoin( + $aliasMember, CoreRequestBuilder::TABLE_CIRCLE, $aliasInvitedBy, + $expr->eq($aliasMember . '.invited_by', $aliasInvitedBy . '.unique_id') + ); + + $this->leftJoinOwner($aliasInvitedBy); + } + + /** * @param string $aliasMember * @param IFederatedUser|null $initiator @@ -811,7 +847,10 @@ public function leftJoinMember( * * @throws RequestBuilderException */ - public function leftJoinInheritedMembers(string $alias, string $field = '', string $aliasInheritedBy = '' + public function leftJoinInheritedMembers( + string $alias, + string $field = '', + string $aliasInheritedBy = '' ): void { $expr = $this->expr(); diff --git a/lib/Db/MemberRequest.php b/lib/Db/MemberRequest.php index 62438c0fd..7b0ebbe79 100644 --- a/lib/Db/MemberRequest.php +++ b/lib/Db/MemberRequest.php @@ -70,7 +70,11 @@ public function save(Member $member): void { ->setValue('level', $qb->createNamedParameter($member->getLevel())) ->setValue('status', $qb->createNamedParameter($member->getStatus())) ->setValue('contact_id', $qb->createNamedParameter($member->getContactId())) - ->setValue('note', $qb->createNamedParameter($member->getNote())); + ->setValue('note', $qb->createNamedParameter(json_encode($member->getNotes()))); + + if ($member->hasInvitedBy()) { + $qb->setValue('invited_by', $qb->createNamedParameter($member->getInvitedBy()->getSingleId())); + } $qb->execute(); } @@ -91,7 +95,7 @@ public function update(Member $member): void { ->set('level', $qb->createNamedParameter($member->getLevel())) ->set('status', $qb->createNamedParameter($member->getStatus())) ->set('contact_id', $qb->createNamedParameter($member->getContactId())) - ->set('note', $qb->createNamedParameter($member->getNote())); + ->set('note', $qb->createNamedParameter(json_encode($member->getNotes()))); $qb->limitToCircleId($member->getCircleId()); $qb->limitToUserId($member->getUserId()); @@ -212,6 +216,7 @@ public function getMembers( $qb->setOptions([CoreQueryBuilder::MEMBER], ['canBeVisitorOnOpen' => true]); $qb->leftJoinCircle(CoreQueryBuilder::MEMBER, $initiator); + $qb->leftJoinInvitedBy(CoreQueryBuilder::MEMBER); if (!is_null($remoteInstance)) { $aliasCircle = $qb->generateAlias(CoreQueryBuilder::MEMBER, CoreQueryBuilder::CIRCLE); @@ -351,7 +356,7 @@ public function searchFederatedUsers(string $needle): array { * @return FederatedUser[]|Member * @throws RequestBuilderException */ - public function getAlternateSingleId(IFederatedUser $federatedUser) { + public function getAlternateSingleId(IFederatedUser $federatedUser): array { $qb = $this->getMemberSelectSql(); $qb->limitToSingleId($federatedUser->getSingleId()); diff --git a/lib/FederatedItems/CircleCreate.php b/lib/FederatedItems/CircleCreate.php index 302e19f0e..b2e623a35 100644 --- a/lib/FederatedItems/CircleCreate.php +++ b/lib/FederatedItems/CircleCreate.php @@ -125,6 +125,10 @@ public function manage(FederatedEvent $event): void { } catch (MemberNotFoundException $e) { } + if ($owner->hasInvitedBy()) { + $owner->setNoteObj('invitedBy', $owner->getInvitedBy()); + } + $this->circleRequest->save($circle); $this->memberRequest->save($owner); $this->membershipService->onUpdate($owner->getSingleId()); diff --git a/lib/FederatedItems/MassiveMemberAdd.php b/lib/FederatedItems/MassiveMemberAdd.php index 0d54422c7..12dd26d45 100644 --- a/lib/FederatedItems/MassiveMemberAdd.php +++ b/lib/FederatedItems/MassiveMemberAdd.php @@ -77,7 +77,7 @@ public function verify(FederatedEvent $event): void { foreach ($members as $member) { try { - $filtered[] = $this->generateMember($circle, $member); + $filtered[] = $this->generateMember($event, $circle, $member); } catch (Exception $e) { } } diff --git a/lib/FederatedItems/SingleMemberAdd.php b/lib/FederatedItems/SingleMemberAdd.php index 700b40f49..eb64e4534 100644 --- a/lib/FederatedItems/SingleMemberAdd.php +++ b/lib/FederatedItems/SingleMemberAdd.php @@ -177,7 +177,7 @@ public function verify(FederatedEvent $event): void { $initiatorHelper = new MemberHelper($initiator); $initiatorHelper->mustBeModerator(); - $member = $this->generateMember($circle, $member); + $member = $this->generateMember($event, $circle, $member); $event->setMembers([$member]); $event->setOutcome($member->jsonSerialize()); @@ -328,7 +328,7 @@ public function result(FederatedEvent $event, array $results): void { * @throws UserTypeNotFoundException * @throws RequestBuilderException */ - protected function generateMember(Circle $circle, Member $member): Member { + protected function generateMember(FederatedEvent $event, Circle $circle, Member $member): Member { try { if ($member->getSingleId() !== '') { $userId = $member->getSingleId() . '@' . $member->getInstance(); @@ -366,6 +366,10 @@ protected function generateMember(Circle $circle, Member $member): Member { $member->importFromIFederatedUser($federatedUser); $member->setCircleId($circle->getSingleId()); $member->setCircle($circle); + + $this->confirmPatron($event, $member); + $member->setNoteObj('invitedBy', $member->getInvitedBy()); + $this->manageMemberStatus($circle, $member); $this->circleService->confirmCircleNotFull($circle); @@ -387,6 +391,7 @@ protected function generateMember(Circle $circle, Member $member): Member { * @param Member $member * * @throws FederatedItemBadRequestException + * @throws RequestBuilderException */ private function manageMemberStatus(Circle $circle, Member $member) { try { @@ -428,6 +433,28 @@ private function manageMemberStatus(Circle $circle, Member $member) { } + /** + * @param FederatedEvent $event + * @param Member $member + * + * @throws FederatedItemBadRequestException + * @throws FederatedUserException + * @throws RequestBuilderException + */ + private function confirmPatron(FederatedEvent $event, Member $member): void { + if (!$member->hasInvitedBy()) { + throw new FederatedItemBadRequestException(StatusCode::$MEMBER_ADD[129], 129); + } + + $patron = $member->getInvitedBy(); + if ($patron->getInstance() !== $event->getOrigin()) { + throw new FederatedItemBadRequestException(StatusCode::$MEMBER_ADD[130], 130); + } + + $this->federatedUserService->confirmLocalSingleId($patron); + } + + /** * @param Member $member * diff --git a/lib/Migration/Version0022Date20220526113601.php b/lib/Migration/Version0022Date20220526113601.php index 917d9c59e..db5de477a 100644 --- a/lib/Migration/Version0022Date20220526113601.php +++ b/lib/Migration/Version0022Date20220526113601.php @@ -210,6 +210,12 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt 'length' => 255 ] ); + $table->addColumn( + 'invited_by', 'string', [ + 'notnull' => false, + 'length' => 31, + ] + ); $table->addColumn( 'level', 'smallint', [ 'notnull' => true, @@ -375,6 +381,11 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt 'notnull' => false ] ); + $table->addColumn( + 'updated', 'datetime', [ + 'notnull' => false, + ] + ); $table->addColumn( 'creation', 'bigint', [ 'length' => 14, diff --git a/lib/Migration/Version0022Date20220601121545.php b/lib/Migration/Version0022Date20220601121545.php new file mode 100644 index 000000000..0fd34cd61 --- /dev/null +++ b/lib/Migration/Version0022Date20220601121545.php @@ -0,0 +1,96 @@ + + * @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\Migration; + +use Closure; +use Doctrine\DBAL\Schema\SchemaException; +use OCP\DB\ISchemaWrapper; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + + +/** + * Class Version0022Date20220526113601 + * + * @package OCA\Circles\Migration + */ +class Version0022Date20220601121545 extends SimpleMigrationStep { + + + /** + * @param IDBConnection $connection + */ + public function __construct(IDBConnection $connection) { + } + + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * + * @return null|ISchemaWrapper + * @throws SchemaException + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + if ($schema->hasTable('circles_event')) { + $table = $schema->getTable('circles_event'); + if (!$table->hasColumn('updated')) { + $table->addColumn( + 'updated', 'datetime', [ + 'notnull' => false, + ] + ); + } + } + + if ($schema->hasTable('circles_member')) { + $table = $schema->getTable('circles_member'); + if (!$table->hasColumn('invited_by')) { + $table->addColumn( + 'invited_by', 'string', [ + 'notnull' => false, + 'length' => 31, + ] + ); + } + } + + return $schema; + } + +} + diff --git a/lib/Model/Circle.php b/lib/Model/Circle.php index 0b76f4fc8..59425aa88 100644 --- a/lib/Model/Circle.php +++ b/lib/Model/Circle.php @@ -147,7 +147,8 @@ class Circle extends ManagedModel implements IMemberships, IDeserializable, INC2 4 => 'Mail Address', 8 => 'Contact', 16 => 'Circle', - 10001 => 'Circles App' + 10001 => 'Circles App', + 10002 => 'occ Command Line' ]; @@ -244,7 +245,6 @@ public function getSingleId(): string { return $this->singleId; } - /** * @return string * @deprecated - removed in NC23 @@ -439,7 +439,7 @@ public function setInheritedMembers(array $members, bool $detailed): IMembership /** * @param bool $detailed * - * @return array + * @return Member[] */ public function getInheritedMembers(bool $detailed = false): array { if (is_null($this->inheritedMembers) @@ -687,7 +687,7 @@ public function jsonSerialize(): array { 'config' => $this->getConfig(), 'description' => $this->getDescription(), 'settings' => $this->getSettings(), - 'url' => $this->getUrl(), + 'url' => $this->getUrl(), 'creation' => $this->getCreation(), 'initiator' => ($this->hasInitiator()) ? $this->getInitiator() : null ]; diff --git a/lib/Model/Member.php b/lib/Model/Member.php index 1b6e896b3..3f5441521 100644 --- a/lib/Model/Member.php +++ b/lib/Model/Member.php @@ -41,6 +41,7 @@ use OCA\Circles\AppInfo\Capabilities; use OCA\Circles\Exceptions\MemberNotFoundException; use OCA\Circles\Exceptions\ParseMemberLevelException; +use OCA\Circles\Exceptions\UnknownInterfaceException; use OCA\Circles\Exceptions\UserTypeNotFoundException; use OCA\Circles\IFederatedUser; use OCA\Circles\IMemberships; @@ -77,7 +78,9 @@ class Member extends ManagedModel implements const TYPE_CONTACT = 8; const TYPE_CIRCLE = 16; const TYPE_APP = 10000; + const APP_CIRCLES = 10001; + const APP_OCC = 10002; public static $TYPE = [ @@ -145,6 +148,9 @@ class Member extends ManagedModel implements /** @var string */ private $instance = ''; + /** @var FederatedUser */ + private $invitedBy; + /** @var RemoteInstance */ private $remoteInstance; @@ -157,8 +163,8 @@ class Member extends ManagedModel implements /** @var string */ private $status = 'Unknown'; - /** @var string */ - private $note = ''; + /** @var array */ + private $notes = []; /** @var string */ private $displayName = ''; @@ -317,6 +323,32 @@ public function isLocal(): bool { } + /** + * @param FederatedUser $invitedBy + * + * @return Member + */ + public function setInvitedBy(FederatedUser $invitedBy): Member { + $this->invitedBy = $invitedBy; + + return $this; + } + + /** + * @return FederatedUser + */ + public function getInvitedBy(): FederatedUser { + return $this->invitedBy; + } + + /** + * @return bool + */ + public function hasInvitedBy(): bool { + return !is_null($this->invitedBy); + } + + /** * @return bool */ @@ -460,21 +492,76 @@ public function getStatus(): string { /** - * @param string $note + * @param array $notes * * @return Member */ - public function setNote(string $note): self { - $this->note = $note; + public function setNotes(array $notes): self { + $this->notes = $notes; return $this; } /** + * @return array + */ + public function getNotes(): array { + return $this->notes; + } + + + /** + * @param string $key + * * @return string */ - public function getNote(): string { - return $this->note; + public function getNote(string $key): string { + return $this->get($key, $this->notes); + } + + /** + * @param string $key + * + * @return array + */ + public function getNoteArray(string $key): array { + return $this->getArray($key, $this->notes); + } + + /** + * @param string $key + * @param string $note + * + * @return $this + */ + public function setNote(string $key, string $note): self { + $this->notes[$key] = $note; + + return $this; + } + + /** + * @param string $key + * @param array $note + * + * @return $this + */ + public function setNoteArray(string $key, array $note): self { + $this->notes[$key] = $note; + + return $this; + } + + /** + * @param string $key + * @param JsonSerializable $obj + * + * @return $this + */ + public function setNoteObj(string $key, JsonSerializable $obj): self { + $this->notes[$key] = $obj; + + return $this; } @@ -679,7 +766,7 @@ public function import(array $data): IDeserializable { $this->setStatus($this->get('status', $data)); $this->setDisplayName($this->get('displayName', $data)); $this->setDisplayUpdate($this->getInt('displayUpdate', $data)); - $this->setNote($this->get('note', $data)); + $this->setNotes($this->getArray('notes', $data)); $this->setContactId($this->get('contactId', $data)); $this->setContactMeta($this->get('contactMeta', $data)); $this->setJoined($this->getInt('joined', $data)); @@ -698,6 +785,13 @@ public function import(array $data): IDeserializable { } catch (InvalidItemException $e) { } + try { + /** @var FederatedUser $invitedBy */ + $invitedBy = $this->deserialize($this->getArray('invitedBy', $data), FederatedUser::class); + $this->setInvitedBy($invitedBy); + } catch (InvalidItemException $e) { + } + try { /** @var FederatedUSer $inheritedBy */ $inheritedBy = $this->deserialize($this->getArray('inheritedBy', $data), Membership::class); @@ -730,7 +824,7 @@ public function importFromDatabase(array $data, string $prefix = ''): INC22Query $this->setLevel($this->getInt($prefix . 'level', $data)); $this->setStatus($this->get($prefix . 'status', $data)); $this->setDisplayName($this->get($prefix . 'cached_name', $data)); - $this->setNote($this->get($prefix . 'note', $data)); + $this->setNotes($this->getArray($prefix . 'note', $data)); $this->setContactId($this->get($prefix . 'contact_id', $data)); $this->setContactMeta($this->get($prefix . 'contact_meta', $data)); @@ -750,12 +844,25 @@ public function importFromDatabase(array $data, string $prefix = ''): INC22Query $this->getManager()->manageImportFromDatabase($this, $data, $prefix); + // in case invitedBy is not obtainable from 'invited_by', we reach data from 'note' + if (!$this->hasInvitedBy()) { + $invitedByArray = $this->getNoteArray('invitedBy'); + if (!empty($invitedByArray)) { + try { + $invitedBy = new FederatedUser(); + $this->setInvitedBy($invitedBy->import($invitedByArray)); + } catch (InvalidItemException $e) { + } + } + } + return $this; } /** * @return string[] + * @throws UnknownInterfaceException */ public function jsonSerialize(): array { $arr = [ @@ -770,12 +877,16 @@ public function jsonSerialize(): array { 'status' => $this->getStatus(), 'displayName' => $this->getDisplayName(), 'displayUpdate' => $this->getDisplayUpdate(), - 'note' => $this->getNote(), + 'notes' => $this->getNotes(), 'contactId' => $this->getContactId(), 'contactMeta' => $this->getContactMeta(), 'joined' => $this->getJoined() ]; + if ($this->hasInvitedBy()) { + $arr['invitedBy'] = $this->getInvitedBy(); + } + if ($this->hasBasedOn()) { $arr['basedOn'] = $this->getBasedOn(); } diff --git a/lib/Model/ModelManager.php b/lib/Model/ModelManager.php index ade9dbbec..71c929dec 100644 --- a/lib/Model/ModelManager.php +++ b/lib/Model/ModelManager.php @@ -40,6 +40,7 @@ use OCA\Circles\Exceptions\FileCacheNotFoundException; use OCA\Circles\Exceptions\MemberNotFoundException; use OCA\Circles\Exceptions\MembershipNotFoundException; +use OCA\Circles\Exceptions\OwnerNotFoundException; use OCA\Circles\Exceptions\RemoteNotFoundException; use OCA\Circles\Exceptions\RequestBuilderException; use OCA\Circles\Exceptions\UnknownInterfaceException; @@ -275,6 +276,17 @@ private function importIntoMember(Member $member, array $data, string $path, str } break; + case CoreQueryBuilder::INVITED_BY; + try { + $invitedByCircle = new Circle(); + $invitedByCircle->importFromDatabase($data, $prefix); + $invitedBy = new FederatedUser(); + $invitedBy->importFromCircle($invitedByCircle); + $member->setInvitedBy($invitedBy); + } catch (CircleNotFoundException | OwnerNotFoundException $e) { + } + break; + case CoreQueryBuilder::INHERITANCE_FROM; try { $inheritanceFrom = new Member(); diff --git a/lib/Service/CircleService.php b/lib/Service/CircleService.php index 0de9da174..719864f3b 100644 --- a/lib/Service/CircleService.php +++ b/lib/Service/CircleService.php @@ -173,6 +173,9 @@ public function create( ->setCircleId($circle->getSingleId()) ->setLevel(Member::LEVEL_OWNER) ->setStatus(Member::STATUS_MEMBER); + + $this->federatedUserService->setMemberPatron($member); + $circle->setOwner($member) ->setInitiator($member); diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php index 8e3a9d2ae..864dfcc2b 100644 --- a/lib/Service/ConfigService.php +++ b/lib/Service/ConfigService.php @@ -558,12 +558,12 @@ public function isLocalInstance(string $instance): bool { * * @return string */ - public function displayInstance(string $instance): string { + public function displayInstance(string $instance, bool $showAt = false): string { if ($this->isLocalInstance($instance)) { return ''; } - return $instance; + return (($showAt) ? '@' : '') . $instance; } diff --git a/lib/Service/FederatedUserService.php b/lib/Service/FederatedUserService.php index 6a3b843fc..3a1c18951 100644 --- a/lib/Service/FederatedUserService.php +++ b/lib/Service/FederatedUserService.php @@ -36,6 +36,7 @@ use daita\MySmallPhpTools\Traits\TArrayTools; use daita\MySmallPhpTools\Traits\TStringTools; use Exception; +use OC; use OCA\Circles\AppInfo\Application; use OCA\Circles\Db\CircleRequest; use OCA\Circles\Db\MemberRequest; @@ -133,6 +134,9 @@ class FederatedUserService { /** @var bool */ private $bypass = false; + /** @var bool */ + private $initiatedByOcc = false; + /** * FederatedUserService constructor. @@ -146,6 +150,7 @@ class FederatedUserService { * @param MemberRequest $memberRequest * @param RemoteService $remoteService * @param ContactService $contactService + * @param InterfaceService $interfaceService * @param ConfigService $configService */ public function __construct( @@ -165,6 +170,10 @@ public function __construct( $this->contactService = $contactService; $this->interfaceService = $interfaceService; $this->configService = $configService; + + if (OC::$CLI) { + $this->setInitiatedByOcc(true); + } } @@ -281,6 +290,40 @@ public function bypassCurrentUserCondition(bool $bypass): void { } + /** + * @param bool $initiatedByOcc + */ + public function setInitiatedByOcc(bool $initiatedByOcc): void { + $this->initiatedByOcc = $initiatedByOcc; + } + + /** + * @return bool + */ + public function isInitiatedByOcc(): bool { + return $this->initiatedByOcc; + } + + /** + * @param Member $member + * + * @throws ContactAddressBookNotFoundException + * @throws ContactFormatException + * @throws ContactNotFoundException + * @throws FederatedUserException + * @throws InvalidIdException + * @throws RequestBuilderException + * @throws SingleCircleNotFoundException + */ + public function setMemberPatron(Member $member): void { + if ($this->isInitiatedByOcc()) { + $member->setInvitedBy($this->getAppInitiator('occ', Member::APP_OCC)); + } else { + $member->setInvitedBy($this->getCurrentUser()); + } + } + + /** * @return FederatedUser|null */ @@ -356,10 +399,13 @@ public function getLocalFederatedUser(string $userId): FederatedUser { * @param int $appNumber * * @return FederatedUser + * @throws ContactAddressBookNotFoundException + * @throws ContactFormatException + * @throws ContactNotFoundException * @throws FederatedUserException * @throws InvalidIdException - * @throws SingleCircleNotFoundException * @throws RequestBuilderException + * @throws SingleCircleNotFoundException */ public function getAppInitiator(string $appId, int $appNumber): FederatedUser { $circle = new Circle(); @@ -380,6 +426,7 @@ public function getAppInitiator(string $appId, int $appNumber): FederatedUser { * TODO: manage non-user type ? * * @param string $userId + * @param int $userType * @param string $circleId * @param bool $bypass * @@ -393,10 +440,10 @@ public function getAppInitiator(string $appId, int $appNumber): FederatedUser { * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException + * @throws RequestBuilderException * @throws SingleCircleNotFoundException * @throws UnknownRemoteException * @throws UserTypeNotFoundException - * @throws RequestBuilderException */ public function commandLineInitiator( string $userId, @@ -701,10 +748,13 @@ public function extractIdAndInstance(string $federatedId): array { /** * @param FederatedUser $federatedUser * + * @throws ContactAddressBookNotFoundException + * @throws ContactFormatException + * @throws ContactNotFoundException * @throws FederatedUserException * @throws InvalidIdException - * @throws SingleCircleNotFoundException * @throws RequestBuilderException + * @throws SingleCircleNotFoundException */ private function fillSingleCircleId(FederatedUser $federatedUser): void { if ($federatedUser->getSingleId() !== '') { @@ -772,6 +822,10 @@ private function getSingleCircle(FederatedUser $federatedUser): Circle { ->setDisplayName($owner->getUserId()) ->setStatus('Member'); + if ($federatedUser->getUserType() !== Member::TYPE_APP) { + $owner->setInvitedBy($this->getAppInitiator(Application::APP_ID, Member::APP_CIRCLES)); + } + $this->memberRequest->save($owner); // TODO: should not be needed // $this->membershipService->onUpdate($id); @@ -822,6 +876,7 @@ private function confirmFederatedUser(FederatedUser $federatedUser): void { * @param FederatedUser $federatedUser * * @throws FederatedUserException + * @throws RequestBuilderException */ public function confirmLocalSingleId(IFederatedUser $federatedUser): void { $members = $this->memberRequest->getMembersBySingleId($federatedUser->getSingleId()); diff --git a/lib/Service/MemberService.php b/lib/Service/MemberService.php index 208f147b4..8ba59898d 100644 --- a/lib/Service/MemberService.php +++ b/lib/Service/MemberService.php @@ -200,6 +200,8 @@ public function addMember(string $circleId, FederatedUser $federatedUser): array $member = new Member(); $member->importFromIFederatedUser($federatedUser); + $this->federatedUserService->setMemberPatron($member); + $event = new FederatedEvent(SingleMemberAdd::class); $event->setCircle($circle); $event->setMember($member); @@ -231,10 +233,17 @@ public function addMembers(string $circleId, array $federatedUsers): array { $this->federatedUserService->mustHaveCurrentUser(); $circle = $this->circleRequest->getCircle($circleId, $this->federatedUserService->getCurrentUser()); + if ($this->federatedUserService->isInitiatedByOcc()) { + $patron = $this->federatedUserService->getAppInitiator('occ', Member::APP_OCC); + } else { + $patron = $this->federatedUserService->getCurrentUser(); + } + $members = array_map( - function(FederatedUser $federatedUser) { + function(FederatedUser $federatedUser) use ($patron) { $member = new Member(); $member->importFromIFederatedUser($federatedUser); + $member->setInvitedBy($patron); return $member; }, $federatedUsers diff --git a/lib/Service/SyncService.php b/lib/Service/SyncService.php index b54636f2b..8b55b2e46 100644 --- a/lib/Service/SyncService.php +++ b/lib/Service/SyncService.php @@ -38,6 +38,9 @@ use OCA\Circles\Db\CircleRequest; use OCA\Circles\Db\MemberRequest; use OCA\Circles\Exceptions\CircleNotFoundException; +use OCA\Circles\Exceptions\ContactAddressBookNotFoundException; +use OCA\Circles\Exceptions\ContactFormatException; +use OCA\Circles\Exceptions\ContactNotFoundException; use OCA\Circles\Exceptions\FederatedEventException; use OCA\Circles\Exceptions\FederatedItemException; use OCA\Circles\Exceptions\FederatedUserException; @@ -167,6 +170,7 @@ private function migrationTo22(): void { * @return void */ public function syncAll(): void { + $this->syncOcc(); $this->syncNextcloudUsers(); $this->syncGlobalScale(); $this->syncRemote(); @@ -175,6 +179,20 @@ public function syncAll(): void { } + /** + * @throws FederatedUserException + * @throws InvalidIdException + * @throws RequestBuilderException + * @throws SingleCircleNotFoundException + * @throws ContactAddressBookNotFoundException + * @throws ContactFormatException + * @throws ContactNotFoundException + */ + public function syncOcc(): void { + $this->federatedUserService->getAppInitiator('occ', Member::APP_OCC); + } + + /** * @return void */ @@ -326,6 +344,7 @@ private function generateGroupMember(Circle $circle, string $userId): Member { $member->setCircleId($circle->getSingleId()); $member->setLevel(Member::LEVEL_MEMBER); $member->setStatus(Member::STATUS_MEMBER); + $member->setInvitedBy($this->federatedUserService->getCurrentApp()); return $member; } diff --git a/lib/StatusCode.php b/lib/StatusCode.php index c79008c1b..13f45a079 100644 --- a/lib/StatusCode.php +++ b/lib/StatusCode.php @@ -67,7 +67,9 @@ class StatusCode { 125 => 'The designed circle cannot be added', 126 => 'Circle only accepts local users', 127 => 'Remote Users are not accepted in a non-federated Circle', - 128 => 'Cannot add Circle as its own Member' + 128 => 'Cannot add Circle as its own Member', + 129 => 'Member does not contains a patron', + 130 => 'Member is invited by an entity that does not belongs to the instance at the origin of the request' ]; static $MEMBER_LEVEL = [