@@ -19,63 +19,63 @@ |
||
| 19 | 19 | |
| 20 | 20 | #[\PHPUnit\Framework\Attributes\Group(name: 'DB')] |
| 21 | 21 | class NotificationProviderManagerTest extends TestCase { |
| 22 | - private NotificationProviderManager $providerManager; |
|
| 22 | + private NotificationProviderManager $providerManager; |
|
| 23 | 23 | |
| 24 | - /** |
|
| 25 | - * @throws ContainerExceptionInterface |
|
| 26 | - */ |
|
| 27 | - protected function setUp(): void { |
|
| 28 | - parent::setUp(); |
|
| 24 | + /** |
|
| 25 | + * @throws ContainerExceptionInterface |
|
| 26 | + */ |
|
| 27 | + protected function setUp(): void { |
|
| 28 | + parent::setUp(); |
|
| 29 | 29 | |
| 30 | - $this->providerManager = new NotificationProviderManager(); |
|
| 31 | - $this->providerManager->registerProvider(EmailProvider::class); |
|
| 32 | - } |
|
| 30 | + $this->providerManager = new NotificationProviderManager(); |
|
| 31 | + $this->providerManager->registerProvider(EmailProvider::class); |
|
| 32 | + } |
|
| 33 | 33 | |
| 34 | - /** |
|
| 35 | - * @throws ProviderNotAvailableException |
|
| 36 | - * @throws NotificationTypeDoesNotExistException |
|
| 37 | - */ |
|
| 38 | - public function testGetProviderForUnknownType(): void { |
|
| 39 | - $this->expectException(NotificationTypeDoesNotExistException::class); |
|
| 40 | - $this->expectExceptionMessage('Type NOT EXISTENT is not an accepted type of notification'); |
|
| 34 | + /** |
|
| 35 | + * @throws ProviderNotAvailableException |
|
| 36 | + * @throws NotificationTypeDoesNotExistException |
|
| 37 | + */ |
|
| 38 | + public function testGetProviderForUnknownType(): void { |
|
| 39 | + $this->expectException(NotificationTypeDoesNotExistException::class); |
|
| 40 | + $this->expectExceptionMessage('Type NOT EXISTENT is not an accepted type of notification'); |
|
| 41 | 41 | |
| 42 | - $this->providerManager->getProvider('NOT EXISTENT'); |
|
| 43 | - } |
|
| 42 | + $this->providerManager->getProvider('NOT EXISTENT'); |
|
| 43 | + } |
|
| 44 | 44 | |
| 45 | - /** |
|
| 46 | - * @throws NotificationTypeDoesNotExistException |
|
| 47 | - * @throws ProviderNotAvailableException |
|
| 48 | - */ |
|
| 49 | - public function testGetProviderForUnRegisteredType(): void { |
|
| 50 | - $this->expectException(ProviderNotAvailableException::class); |
|
| 51 | - $this->expectExceptionMessage('No notification provider for type AUDIO available'); |
|
| 45 | + /** |
|
| 46 | + * @throws NotificationTypeDoesNotExistException |
|
| 47 | + * @throws ProviderNotAvailableException |
|
| 48 | + */ |
|
| 49 | + public function testGetProviderForUnRegisteredType(): void { |
|
| 50 | + $this->expectException(ProviderNotAvailableException::class); |
|
| 51 | + $this->expectExceptionMessage('No notification provider for type AUDIO available'); |
|
| 52 | 52 | |
| 53 | - $this->providerManager->getProvider('AUDIO'); |
|
| 54 | - } |
|
| 53 | + $this->providerManager->getProvider('AUDIO'); |
|
| 54 | + } |
|
| 55 | 55 | |
| 56 | - public function testGetProvider(): void { |
|
| 57 | - $provider = $this->providerManager->getProvider('EMAIL'); |
|
| 58 | - $this->assertInstanceOf(EmailProvider::class, $provider); |
|
| 59 | - } |
|
| 56 | + public function testGetProvider(): void { |
|
| 57 | + $provider = $this->providerManager->getProvider('EMAIL'); |
|
| 58 | + $this->assertInstanceOf(EmailProvider::class, $provider); |
|
| 59 | + } |
|
| 60 | 60 | |
| 61 | - public function testRegisterProvider(): void { |
|
| 62 | - $this->providerManager->registerProvider(PushProvider::class); |
|
| 63 | - $provider = $this->providerManager->getProvider('DISPLAY'); |
|
| 64 | - $this->assertInstanceOf(PushProvider::class, $provider); |
|
| 65 | - } |
|
| 61 | + public function testRegisterProvider(): void { |
|
| 62 | + $this->providerManager->registerProvider(PushProvider::class); |
|
| 63 | + $provider = $this->providerManager->getProvider('DISPLAY'); |
|
| 64 | + $this->assertInstanceOf(PushProvider::class, $provider); |
|
| 65 | + } |
|
| 66 | 66 | |
| 67 | - /** |
|
| 68 | - * @throws ContainerExceptionInterface |
|
| 69 | - */ |
|
| 70 | - public function testRegisterBadProvider(): void { |
|
| 71 | - $this->expectException(\InvalidArgumentException::class); |
|
| 72 | - $this->expectExceptionMessage('Invalid notification provider registered'); |
|
| 67 | + /** |
|
| 68 | + * @throws ContainerExceptionInterface |
|
| 69 | + */ |
|
| 70 | + public function testRegisterBadProvider(): void { |
|
| 71 | + $this->expectException(\InvalidArgumentException::class); |
|
| 72 | + $this->expectExceptionMessage('Invalid notification provider registered'); |
|
| 73 | 73 | |
| 74 | - $this->providerManager->registerProvider(Capabilities::class); |
|
| 75 | - } |
|
| 74 | + $this->providerManager->registerProvider(Capabilities::class); |
|
| 75 | + } |
|
| 76 | 76 | |
| 77 | - public function testHasProvider(): void { |
|
| 78 | - $this->assertTrue($this->providerManager->hasProvider('EMAIL')); |
|
| 79 | - $this->assertFalse($this->providerManager->hasProvider('EMAIL123')); |
|
| 80 | - } |
|
| 77 | + public function testHasProvider(): void { |
|
| 78 | + $this->assertTrue($this->providerManager->hasProvider('EMAIL')); |
|
| 79 | + $this->assertFalse($this->providerManager->hasProvider('EMAIL123')); |
|
| 80 | + } |
|
| 81 | 81 | } |
@@ -19,51 +19,51 @@ |
||
| 19 | 19 | */ |
| 20 | 20 | class NotificationProviderManager { |
| 21 | 21 | |
| 22 | - /** @var INotificationProvider[] */ |
|
| 23 | - private $providers = []; |
|
| 22 | + /** @var INotificationProvider[] */ |
|
| 23 | + private $providers = []; |
|
| 24 | 24 | |
| 25 | - /** |
|
| 26 | - * Checks whether a provider for a given ACTION exists |
|
| 27 | - * |
|
| 28 | - * @param string $type |
|
| 29 | - * @return bool |
|
| 30 | - */ |
|
| 31 | - public function hasProvider(string $type):bool { |
|
| 32 | - return (\in_array($type, ReminderService::REMINDER_TYPES, true) |
|
| 33 | - && isset($this->providers[$type])); |
|
| 34 | - } |
|
| 25 | + /** |
|
| 26 | + * Checks whether a provider for a given ACTION exists |
|
| 27 | + * |
|
| 28 | + * @param string $type |
|
| 29 | + * @return bool |
|
| 30 | + */ |
|
| 31 | + public function hasProvider(string $type):bool { |
|
| 32 | + return (\in_array($type, ReminderService::REMINDER_TYPES, true) |
|
| 33 | + && isset($this->providers[$type])); |
|
| 34 | + } |
|
| 35 | 35 | |
| 36 | - /** |
|
| 37 | - * Get provider for a given ACTION |
|
| 38 | - * |
|
| 39 | - * @param string $type |
|
| 40 | - * @return INotificationProvider |
|
| 41 | - * @throws NotificationProvider\ProviderNotAvailableException |
|
| 42 | - * @throws NotificationTypeDoesNotExistException |
|
| 43 | - */ |
|
| 44 | - public function getProvider(string $type):INotificationProvider { |
|
| 45 | - if (in_array($type, ReminderService::REMINDER_TYPES, true)) { |
|
| 46 | - if (isset($this->providers[$type])) { |
|
| 47 | - return $this->providers[$type]; |
|
| 48 | - } |
|
| 49 | - throw new ProviderNotAvailableException($type); |
|
| 50 | - } |
|
| 51 | - throw new NotificationTypeDoesNotExistException($type); |
|
| 52 | - } |
|
| 36 | + /** |
|
| 37 | + * Get provider for a given ACTION |
|
| 38 | + * |
|
| 39 | + * @param string $type |
|
| 40 | + * @return INotificationProvider |
|
| 41 | + * @throws NotificationProvider\ProviderNotAvailableException |
|
| 42 | + * @throws NotificationTypeDoesNotExistException |
|
| 43 | + */ |
|
| 44 | + public function getProvider(string $type):INotificationProvider { |
|
| 45 | + if (in_array($type, ReminderService::REMINDER_TYPES, true)) { |
|
| 46 | + if (isset($this->providers[$type])) { |
|
| 47 | + return $this->providers[$type]; |
|
| 48 | + } |
|
| 49 | + throw new ProviderNotAvailableException($type); |
|
| 50 | + } |
|
| 51 | + throw new NotificationTypeDoesNotExistException($type); |
|
| 52 | + } |
|
| 53 | 53 | |
| 54 | - /** |
|
| 55 | - * Registers a new provider |
|
| 56 | - * |
|
| 57 | - * @param string $providerClassName |
|
| 58 | - * @throws ContainerExceptionInterface |
|
| 59 | - */ |
|
| 60 | - public function registerProvider(string $providerClassName):void { |
|
| 61 | - $provider = Server::get($providerClassName); |
|
| 54 | + /** |
|
| 55 | + * Registers a new provider |
|
| 56 | + * |
|
| 57 | + * @param string $providerClassName |
|
| 58 | + * @throws ContainerExceptionInterface |
|
| 59 | + */ |
|
| 60 | + public function registerProvider(string $providerClassName):void { |
|
| 61 | + $provider = Server::get($providerClassName); |
|
| 62 | 62 | |
| 63 | - if (!$provider instanceof INotificationProvider) { |
|
| 64 | - throw new \InvalidArgumentException('Invalid notification provider registered'); |
|
| 65 | - } |
|
| 63 | + if (!$provider instanceof INotificationProvider) { |
|
| 64 | + throw new \InvalidArgumentException('Invalid notification provider registered'); |
|
| 65 | + } |
|
| 66 | 66 | |
| 67 | - $this->providers[$provider::NOTIFICATION_TYPE] = $provider; |
|
| 68 | - } |
|
| 67 | + $this->providers[$provider::NOTIFICATION_TYPE] = $provider; |
|
| 68 | + } |
|
| 69 | 69 | } |
@@ -33,112 +33,112 @@ |
||
| 33 | 33 | use function array_map; |
| 34 | 34 | |
| 35 | 35 | class UserAddressBooks extends \Sabre\CardDAV\AddressBookHome { |
| 36 | - protected IL10N $l10n; |
|
| 37 | - protected IConfig $config; |
|
| 38 | - protected IAppConfig $appConfig; |
|
| 36 | + protected IL10N $l10n; |
|
| 37 | + protected IConfig $config; |
|
| 38 | + protected IAppConfig $appConfig; |
|
| 39 | 39 | |
| 40 | - public function __construct( |
|
| 41 | - Backend\BackendInterface $carddavBackend, |
|
| 42 | - string $principalUri, |
|
| 43 | - private PluginManager $pluginManager, |
|
| 44 | - private ?IUser $user, |
|
| 45 | - private ?IGroupManager $groupManager, |
|
| 46 | - ) { |
|
| 47 | - parent::__construct($carddavBackend, $principalUri); |
|
| 40 | + public function __construct( |
|
| 41 | + Backend\BackendInterface $carddavBackend, |
|
| 42 | + string $principalUri, |
|
| 43 | + private PluginManager $pluginManager, |
|
| 44 | + private ?IUser $user, |
|
| 45 | + private ?IGroupManager $groupManager, |
|
| 46 | + ) { |
|
| 47 | + parent::__construct($carddavBackend, $principalUri); |
|
| 48 | 48 | |
| 49 | - $this->l10n = Util::getL10N('dav'); |
|
| 50 | - $this->config = Server::get(IConfig::class); |
|
| 51 | - $this->appConfig = Server::get(IAppConfig::class); |
|
| 52 | - } |
|
| 49 | + $this->l10n = Util::getL10N('dav'); |
|
| 50 | + $this->config = Server::get(IConfig::class); |
|
| 51 | + $this->appConfig = Server::get(IAppConfig::class); |
|
| 52 | + } |
|
| 53 | 53 | |
| 54 | - /** |
|
| 55 | - * Returns a list of address books |
|
| 56 | - * |
|
| 57 | - * @return IAddressBook[] |
|
| 58 | - */ |
|
| 59 | - public function getChildren() { |
|
| 60 | - /** @var string|array $principal */ |
|
| 61 | - $principal = $this->principalUri; |
|
| 62 | - $addressBooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri); |
|
| 63 | - // add the system address book |
|
| 64 | - $systemAddressBook = null; |
|
| 65 | - $systemAddressBookExposed = $this->appConfig->getValueBool(Application::APP_ID, ConfigLexicon::SYSTEM_ADDRESSBOOK_EXPOSED); |
|
| 66 | - if ($systemAddressBookExposed && is_string($principal) && $principal !== 'principals/system/system' && $this->carddavBackend instanceof CardDavBackend) { |
|
| 67 | - $systemAddressBook = $this->carddavBackend->getAddressBooksByUri('principals/system/system', 'system'); |
|
| 68 | - if ($systemAddressBook !== null) { |
|
| 69 | - $systemAddressBook['uri'] = SystemAddressbook::URI_SHARED; |
|
| 70 | - } |
|
| 71 | - } |
|
| 72 | - if (!is_null($systemAddressBook)) { |
|
| 73 | - $addressBooks[] = $systemAddressBook; |
|
| 74 | - } |
|
| 54 | + /** |
|
| 55 | + * Returns a list of address books |
|
| 56 | + * |
|
| 57 | + * @return IAddressBook[] |
|
| 58 | + */ |
|
| 59 | + public function getChildren() { |
|
| 60 | + /** @var string|array $principal */ |
|
| 61 | + $principal = $this->principalUri; |
|
| 62 | + $addressBooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri); |
|
| 63 | + // add the system address book |
|
| 64 | + $systemAddressBook = null; |
|
| 65 | + $systemAddressBookExposed = $this->appConfig->getValueBool(Application::APP_ID, ConfigLexicon::SYSTEM_ADDRESSBOOK_EXPOSED); |
|
| 66 | + if ($systemAddressBookExposed && is_string($principal) && $principal !== 'principals/system/system' && $this->carddavBackend instanceof CardDavBackend) { |
|
| 67 | + $systemAddressBook = $this->carddavBackend->getAddressBooksByUri('principals/system/system', 'system'); |
|
| 68 | + if ($systemAddressBook !== null) { |
|
| 69 | + $systemAddressBook['uri'] = SystemAddressbook::URI_SHARED; |
|
| 70 | + } |
|
| 71 | + } |
|
| 72 | + if (!is_null($systemAddressBook)) { |
|
| 73 | + $addressBooks[] = $systemAddressBook; |
|
| 74 | + } |
|
| 75 | 75 | |
| 76 | - $objects = []; |
|
| 77 | - if (!empty($addressBooks)) { |
|
| 78 | - /** @var IAddressBook[] $objects */ |
|
| 79 | - $objects = array_map(function (array $addressBook) { |
|
| 80 | - $trustedServers = null; |
|
| 81 | - $request = null; |
|
| 82 | - try { |
|
| 83 | - $trustedServers = Server::get(TrustedServers::class); |
|
| 84 | - $request = Server::get(IRequest::class); |
|
| 85 | - } catch (NotFoundExceptionInterface|ContainerExceptionInterface $e) { |
|
| 86 | - // nothing to do, the request / trusted servers don't exist |
|
| 87 | - } |
|
| 88 | - if ($addressBook['principaluri'] === 'principals/system/system') { |
|
| 89 | - return new SystemAddressbook( |
|
| 90 | - $this->carddavBackend, |
|
| 91 | - $addressBook, |
|
| 92 | - $this->l10n, |
|
| 93 | - $this->config, |
|
| 94 | - Server::get(IUserSession::class), |
|
| 95 | - $request, |
|
| 96 | - $trustedServers, |
|
| 97 | - $this->groupManager |
|
| 98 | - ); |
|
| 99 | - } |
|
| 76 | + $objects = []; |
|
| 77 | + if (!empty($addressBooks)) { |
|
| 78 | + /** @var IAddressBook[] $objects */ |
|
| 79 | + $objects = array_map(function (array $addressBook) { |
|
| 80 | + $trustedServers = null; |
|
| 81 | + $request = null; |
|
| 82 | + try { |
|
| 83 | + $trustedServers = Server::get(TrustedServers::class); |
|
| 84 | + $request = Server::get(IRequest::class); |
|
| 85 | + } catch (NotFoundExceptionInterface|ContainerExceptionInterface $e) { |
|
| 86 | + // nothing to do, the request / trusted servers don't exist |
|
| 87 | + } |
|
| 88 | + if ($addressBook['principaluri'] === 'principals/system/system') { |
|
| 89 | + return new SystemAddressbook( |
|
| 90 | + $this->carddavBackend, |
|
| 91 | + $addressBook, |
|
| 92 | + $this->l10n, |
|
| 93 | + $this->config, |
|
| 94 | + Server::get(IUserSession::class), |
|
| 95 | + $request, |
|
| 96 | + $trustedServers, |
|
| 97 | + $this->groupManager |
|
| 98 | + ); |
|
| 99 | + } |
|
| 100 | 100 | |
| 101 | - return new AddressBook($this->carddavBackend, $addressBook, $this->l10n); |
|
| 102 | - }, $addressBooks); |
|
| 103 | - } |
|
| 104 | - /** @var IAddressBook[][] $objectsFromPlugins */ |
|
| 105 | - $objectsFromPlugins = array_map(function (IAddressBookProvider $plugin): array { |
|
| 106 | - return $plugin->fetchAllForAddressBookHome($this->principalUri); |
|
| 107 | - }, $this->pluginManager->getAddressBookPlugins()); |
|
| 101 | + return new AddressBook($this->carddavBackend, $addressBook, $this->l10n); |
|
| 102 | + }, $addressBooks); |
|
| 103 | + } |
|
| 104 | + /** @var IAddressBook[][] $objectsFromPlugins */ |
|
| 105 | + $objectsFromPlugins = array_map(function (IAddressBookProvider $plugin): array { |
|
| 106 | + return $plugin->fetchAllForAddressBookHome($this->principalUri); |
|
| 107 | + }, $this->pluginManager->getAddressBookPlugins()); |
|
| 108 | 108 | |
| 109 | - return array_merge($objects, ...$objectsFromPlugins); |
|
| 110 | - } |
|
| 109 | + return array_merge($objects, ...$objectsFromPlugins); |
|
| 110 | + } |
|
| 111 | 111 | |
| 112 | - public function createExtendedCollection($name, MkCol $mkCol) { |
|
| 113 | - if (ExternalAddressBook::doesViolateReservedName($name)) { |
|
| 114 | - throw new MethodNotAllowed('The resource you tried to create has a reserved name'); |
|
| 115 | - } |
|
| 112 | + public function createExtendedCollection($name, MkCol $mkCol) { |
|
| 113 | + if (ExternalAddressBook::doesViolateReservedName($name)) { |
|
| 114 | + throw new MethodNotAllowed('The resource you tried to create has a reserved name'); |
|
| 115 | + } |
|
| 116 | 116 | |
| 117 | - parent::createExtendedCollection($name, $mkCol); |
|
| 118 | - } |
|
| 117 | + parent::createExtendedCollection($name, $mkCol); |
|
| 118 | + } |
|
| 119 | 119 | |
| 120 | - /** |
|
| 121 | - * Returns a list of ACE's for this node. |
|
| 122 | - * |
|
| 123 | - * Each ACE has the following properties: |
|
| 124 | - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are |
|
| 125 | - * currently the only supported privileges |
|
| 126 | - * * 'principal', a url to the principal who owns the node |
|
| 127 | - * * 'protected' (optional), indicating that this ACE is not allowed to |
|
| 128 | - * be updated. |
|
| 129 | - * |
|
| 130 | - * @return array |
|
| 131 | - */ |
|
| 132 | - public function getACL() { |
|
| 133 | - $acl = parent::getACL(); |
|
| 134 | - if ($this->principalUri === 'principals/system/system') { |
|
| 135 | - $acl[] = [ |
|
| 136 | - 'privilege' => '{DAV:}read', |
|
| 137 | - 'principal' => '{DAV:}authenticated', |
|
| 138 | - 'protected' => true, |
|
| 139 | - ]; |
|
| 140 | - } |
|
| 120 | + /** |
|
| 121 | + * Returns a list of ACE's for this node. |
|
| 122 | + * |
|
| 123 | + * Each ACE has the following properties: |
|
| 124 | + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are |
|
| 125 | + * currently the only supported privileges |
|
| 126 | + * * 'principal', a url to the principal who owns the node |
|
| 127 | + * * 'protected' (optional), indicating that this ACE is not allowed to |
|
| 128 | + * be updated. |
|
| 129 | + * |
|
| 130 | + * @return array |
|
| 131 | + */ |
|
| 132 | + public function getACL() { |
|
| 133 | + $acl = parent::getACL(); |
|
| 134 | + if ($this->principalUri === 'principals/system/system') { |
|
| 135 | + $acl[] = [ |
|
| 136 | + 'privilege' => '{DAV:}read', |
|
| 137 | + 'principal' => '{DAV:}authenticated', |
|
| 138 | + 'protected' => true, |
|
| 139 | + ]; |
|
| 140 | + } |
|
| 141 | 141 | |
| 142 | - return $acl; |
|
| 143 | - } |
|
| 142 | + return $acl; |
|
| 143 | + } |
|
| 144 | 144 | } |
@@ -76,13 +76,13 @@ discard block |
||
| 76 | 76 | $objects = []; |
| 77 | 77 | if (!empty($addressBooks)) { |
| 78 | 78 | /** @var IAddressBook[] $objects */ |
| 79 | - $objects = array_map(function (array $addressBook) { |
|
| 79 | + $objects = array_map(function(array $addressBook) { |
|
| 80 | 80 | $trustedServers = null; |
| 81 | 81 | $request = null; |
| 82 | 82 | try { |
| 83 | 83 | $trustedServers = Server::get(TrustedServers::class); |
| 84 | 84 | $request = Server::get(IRequest::class); |
| 85 | - } catch (NotFoundExceptionInterface|ContainerExceptionInterface $e) { |
|
| 85 | + } catch (NotFoundExceptionInterface | ContainerExceptionInterface $e) { |
|
| 86 | 86 | // nothing to do, the request / trusted servers don't exist |
| 87 | 87 | } |
| 88 | 88 | if ($addressBook['principaluri'] === 'principals/system/system') { |
@@ -102,7 +102,7 @@ discard block |
||
| 102 | 102 | }, $addressBooks); |
| 103 | 103 | } |
| 104 | 104 | /** @var IAddressBook[][] $objectsFromPlugins */ |
| 105 | - $objectsFromPlugins = array_map(function (IAddressBookProvider $plugin): array { |
|
| 105 | + $objectsFromPlugins = array_map(function(IAddressBookProvider $plugin): array { |
|
| 106 | 106 | return $plugin->fetchAllForAddressBookHome($this->principalUri); |
| 107 | 107 | }, $this->pluginManager->getAddressBookPlugins()); |
| 108 | 108 | |
@@ -33,601 +33,601 @@ |
||
| 33 | 33 | |
| 34 | 34 | class Principal implements BackendInterface { |
| 35 | 35 | |
| 36 | - /** @var string */ |
|
| 37 | - private $principalPrefix; |
|
| 38 | - |
|
| 39 | - /** @var bool */ |
|
| 40 | - private $hasGroups; |
|
| 41 | - |
|
| 42 | - /** @var bool */ |
|
| 43 | - private $hasCircles; |
|
| 44 | - |
|
| 45 | - /** @var KnownUserService */ |
|
| 46 | - private $knownUserService; |
|
| 47 | - |
|
| 48 | - public function __construct( |
|
| 49 | - private IUserManager $userManager, |
|
| 50 | - private IGroupManager $groupManager, |
|
| 51 | - private IAccountManager $accountManager, |
|
| 52 | - private IShareManager $shareManager, |
|
| 53 | - private IUserSession $userSession, |
|
| 54 | - private IAppManager $appManager, |
|
| 55 | - private ProxyMapper $proxyMapper, |
|
| 56 | - KnownUserService $knownUserService, |
|
| 57 | - private IConfig $config, |
|
| 58 | - private IFactory $languageFactory, |
|
| 59 | - string $principalPrefix = 'principals/users/', |
|
| 60 | - ) { |
|
| 61 | - $this->principalPrefix = trim($principalPrefix, '/'); |
|
| 62 | - $this->hasGroups = $this->hasCircles = ($principalPrefix === 'principals/users/'); |
|
| 63 | - $this->knownUserService = $knownUserService; |
|
| 64 | - } |
|
| 65 | - |
|
| 66 | - use PrincipalProxyTrait { |
|
| 67 | - getGroupMembership as protected traitGetGroupMembership; |
|
| 68 | - } |
|
| 69 | - |
|
| 70 | - /** |
|
| 71 | - * Returns a list of principals based on a prefix. |
|
| 72 | - * |
|
| 73 | - * This prefix will often contain something like 'principals'. You are only |
|
| 74 | - * expected to return principals that are in this base path. |
|
| 75 | - * |
|
| 76 | - * You are expected to return at least a 'uri' for every user, you can |
|
| 77 | - * return any additional properties if you wish so. Common properties are: |
|
| 78 | - * {DAV:}displayname |
|
| 79 | - * |
|
| 80 | - * @param string $prefixPath |
|
| 81 | - * @return string[] |
|
| 82 | - */ |
|
| 83 | - public function getPrincipalsByPrefix($prefixPath) { |
|
| 84 | - $principals = []; |
|
| 85 | - |
|
| 86 | - if ($prefixPath === $this->principalPrefix) { |
|
| 87 | - foreach ($this->userManager->search('') as $user) { |
|
| 88 | - $principals[] = $this->userToPrincipal($user); |
|
| 89 | - } |
|
| 90 | - } |
|
| 91 | - |
|
| 92 | - return $principals; |
|
| 93 | - } |
|
| 94 | - |
|
| 95 | - /** |
|
| 96 | - * Returns a specific principal, specified by it's path. |
|
| 97 | - * The returned structure should be the exact same as from |
|
| 98 | - * getPrincipalsByPrefix. |
|
| 99 | - * |
|
| 100 | - * @param string $path |
|
| 101 | - * @return array |
|
| 102 | - */ |
|
| 103 | - public function getPrincipalByPath($path) { |
|
| 104 | - return $this->getPrincipalPropertiesByPath($path); |
|
| 105 | - } |
|
| 106 | - |
|
| 107 | - /** |
|
| 108 | - * Returns a specific principal, specified by its path. |
|
| 109 | - * The returned structure should be the exact same as from |
|
| 110 | - * getPrincipalsByPrefix. |
|
| 111 | - * |
|
| 112 | - * It is possible to optionally filter retrieved properties in case only a limited set is |
|
| 113 | - * required. Note that the implementation might return more properties than requested. |
|
| 114 | - * |
|
| 115 | - * @param string $path The path of the principal |
|
| 116 | - * @param string[]|null $propertyFilter A list of properties to be retrieved or all if null. An empty array will cause a very shallow principal to be retrieved. |
|
| 117 | - */ |
|
| 118 | - public function getPrincipalPropertiesByPath($path, ?array $propertyFilter = null): ?array { |
|
| 119 | - [$prefix, $name] = \Sabre\Uri\split($path); |
|
| 120 | - $decodedName = urldecode($name); |
|
| 121 | - |
|
| 122 | - if ($name === 'calendar-proxy-write' || $name === 'calendar-proxy-read') { |
|
| 123 | - [$prefix2, $name2] = \Sabre\Uri\split($prefix); |
|
| 124 | - |
|
| 125 | - if ($prefix2 === $this->principalPrefix) { |
|
| 126 | - $user = $this->userManager->get($name2); |
|
| 127 | - |
|
| 128 | - if ($user !== null) { |
|
| 129 | - return [ |
|
| 130 | - 'uri' => 'principals/users/' . $user->getUID() . '/' . $name, |
|
| 131 | - ]; |
|
| 132 | - } |
|
| 133 | - return null; |
|
| 134 | - } |
|
| 135 | - } |
|
| 136 | - |
|
| 137 | - if ($prefix === $this->principalPrefix) { |
|
| 138 | - // Depending on where it is called, it may happen that this function |
|
| 139 | - // is called either with a urlencoded version of the name or with a non-urlencoded one. |
|
| 140 | - // The urldecode function replaces %## and +, both of which are forbidden in usernames. |
|
| 141 | - // Hence there can be no ambiguity here and it is safe to call urldecode on all usernames |
|
| 142 | - $user = $this->userManager->get($decodedName); |
|
| 143 | - |
|
| 144 | - if ($user !== null) { |
|
| 145 | - return $this->userToPrincipal($user, $propertyFilter); |
|
| 146 | - } |
|
| 147 | - } elseif ($prefix === 'principals/circles') { |
|
| 148 | - if ($this->userSession->getUser() !== null) { |
|
| 149 | - // At the time of writing - 2021-01-19 — a mixed state is possible. |
|
| 150 | - // The second condition can be removed when this is fixed. |
|
| 151 | - return $this->circleToPrincipal($decodedName) |
|
| 152 | - ?: $this->circleToPrincipal($name); |
|
| 153 | - } |
|
| 154 | - } elseif ($prefix === 'principals/groups') { |
|
| 155 | - // At the time of writing - 2021-01-19 — a mixed state is possible. |
|
| 156 | - // The second condition can be removed when this is fixed. |
|
| 157 | - $group = $this->groupManager->get($decodedName) |
|
| 158 | - ?: $this->groupManager->get($name); |
|
| 159 | - if ($group instanceof IGroup) { |
|
| 160 | - return [ |
|
| 161 | - 'uri' => 'principals/groups/' . $name, |
|
| 162 | - '{DAV:}displayname' => $group->getDisplayName(), |
|
| 163 | - ]; |
|
| 164 | - } |
|
| 165 | - } elseif ($prefix === 'principals/system') { |
|
| 166 | - return [ |
|
| 167 | - 'uri' => 'principals/system/' . $name, |
|
| 168 | - '{DAV:}displayname' => $this->languageFactory->get('dav')->t('Accounts'), |
|
| 169 | - ]; |
|
| 170 | - } elseif ($prefix === 'principals/shares') { |
|
| 171 | - return [ |
|
| 172 | - 'uri' => 'principals/shares/' . $name, |
|
| 173 | - '{DAV:}displayname' => $name, |
|
| 174 | - ]; |
|
| 175 | - } |
|
| 176 | - return null; |
|
| 177 | - } |
|
| 178 | - |
|
| 179 | - /** |
|
| 180 | - * Returns the list of groups a principal is a member of |
|
| 181 | - * |
|
| 182 | - * @param string $principal |
|
| 183 | - * @param bool $needGroups |
|
| 184 | - * @return array |
|
| 185 | - * @throws Exception |
|
| 186 | - */ |
|
| 187 | - public function getGroupMembership($principal, $needGroups = false) { |
|
| 188 | - [$prefix, $name] = \Sabre\Uri\split($principal); |
|
| 189 | - |
|
| 190 | - if ($prefix !== $this->principalPrefix) { |
|
| 191 | - return []; |
|
| 192 | - } |
|
| 193 | - |
|
| 194 | - $user = $this->userManager->get($name); |
|
| 195 | - if (!$user) { |
|
| 196 | - throw new Exception('Principal not found'); |
|
| 197 | - } |
|
| 198 | - |
|
| 199 | - $groups = []; |
|
| 200 | - |
|
| 201 | - if ($this->hasGroups || $needGroups) { |
|
| 202 | - $userGroups = $this->groupManager->getUserGroups($user); |
|
| 203 | - foreach ($userGroups as $userGroup) { |
|
| 204 | - if ($userGroup->hideFromCollaboration()) { |
|
| 205 | - continue; |
|
| 206 | - } |
|
| 207 | - $groups[] = 'principals/groups/' . urlencode($userGroup->getGID()); |
|
| 208 | - } |
|
| 209 | - } |
|
| 210 | - |
|
| 211 | - $groups = array_unique(array_merge( |
|
| 212 | - $groups, |
|
| 213 | - $this->traitGetGroupMembership($principal, $needGroups) |
|
| 214 | - )); |
|
| 215 | - |
|
| 216 | - return $groups; |
|
| 217 | - } |
|
| 218 | - |
|
| 219 | - /** |
|
| 220 | - * @param string $path |
|
| 221 | - * @param PropPatch $propPatch |
|
| 222 | - * @return int |
|
| 223 | - */ |
|
| 224 | - public function updatePrincipal($path, PropPatch $propPatch) { |
|
| 225 | - // Updating schedule-default-calendar-URL is handled in CustomPropertiesBackend |
|
| 226 | - return 0; |
|
| 227 | - } |
|
| 228 | - |
|
| 229 | - /** |
|
| 230 | - * Search user principals |
|
| 231 | - * |
|
| 232 | - * @param array $searchProperties |
|
| 233 | - * @param string $test |
|
| 234 | - * @return array |
|
| 235 | - */ |
|
| 236 | - protected function searchUserPrincipals(array $searchProperties, $test = 'allof') { |
|
| 237 | - $results = []; |
|
| 238 | - |
|
| 239 | - // If sharing is disabled, return the empty array |
|
| 240 | - $shareAPIEnabled = $this->shareManager->shareApiEnabled(); |
|
| 241 | - if (!$shareAPIEnabled) { |
|
| 242 | - return []; |
|
| 243 | - } |
|
| 244 | - |
|
| 245 | - $allowEnumeration = $this->shareManager->allowEnumeration(); |
|
| 246 | - $limitEnumerationGroup = $this->shareManager->limitEnumerationToGroups(); |
|
| 247 | - $limitEnumerationPhone = $this->shareManager->limitEnumerationToPhone(); |
|
| 248 | - $allowEnumerationFullMatch = $this->shareManager->allowEnumerationFullMatch(); |
|
| 249 | - $ignoreSecondDisplayName = $this->shareManager->ignoreSecondDisplayName(); |
|
| 250 | - $matchEmail = $this->shareManager->matchEmail(); |
|
| 251 | - |
|
| 252 | - // If sharing is restricted to group members only, |
|
| 253 | - // return only members that have groups in common |
|
| 254 | - $restrictGroups = false; |
|
| 255 | - $currentUser = $this->userSession->getUser(); |
|
| 256 | - if ($this->shareManager->shareWithGroupMembersOnly()) { |
|
| 257 | - if (!$currentUser instanceof IUser) { |
|
| 258 | - return []; |
|
| 259 | - } |
|
| 260 | - |
|
| 261 | - $restrictGroups = $this->groupManager->getUserGroupIds($currentUser); |
|
| 262 | - } |
|
| 263 | - |
|
| 264 | - $currentUserGroups = []; |
|
| 265 | - if ($limitEnumerationGroup) { |
|
| 266 | - if ($currentUser instanceof IUser) { |
|
| 267 | - $currentUserGroups = $this->groupManager->getUserGroupIds($currentUser); |
|
| 268 | - } |
|
| 269 | - } |
|
| 270 | - |
|
| 271 | - $searchLimit = $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT); |
|
| 272 | - if ($searchLimit <= 0) { |
|
| 273 | - $searchLimit = null; |
|
| 274 | - } |
|
| 275 | - foreach ($searchProperties as $prop => $value) { |
|
| 276 | - switch ($prop) { |
|
| 277 | - case '{http://sabredav.org/ns}email-address': |
|
| 278 | - if (!$allowEnumeration) { |
|
| 279 | - if ($allowEnumerationFullMatch && $matchEmail) { |
|
| 280 | - $users = $this->userManager->getByEmail($value); |
|
| 281 | - } else { |
|
| 282 | - $users = []; |
|
| 283 | - } |
|
| 284 | - } else { |
|
| 285 | - $users = $this->userManager->getByEmail($value); |
|
| 286 | - $users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $allowEnumerationFullMatch, $currentUserGroups) { |
|
| 287 | - if ($allowEnumerationFullMatch && $user->getSystemEMailAddress() === $value) { |
|
| 288 | - return true; |
|
| 289 | - } |
|
| 290 | - |
|
| 291 | - if ($limitEnumerationPhone |
|
| 292 | - && $currentUser instanceof IUser |
|
| 293 | - && $this->knownUserService->isKnownToUser($currentUser->getUID(), $user->getUID())) { |
|
| 294 | - // Synced phonebook match |
|
| 295 | - return true; |
|
| 296 | - } |
|
| 297 | - |
|
| 298 | - if (!$limitEnumerationGroup) { |
|
| 299 | - // No limitation on enumeration, all allowed |
|
| 300 | - return true; |
|
| 301 | - } |
|
| 302 | - |
|
| 303 | - return !empty($currentUserGroups) && !empty(array_intersect( |
|
| 304 | - $this->groupManager->getUserGroupIds($user), |
|
| 305 | - $currentUserGroups |
|
| 306 | - )); |
|
| 307 | - }); |
|
| 308 | - } |
|
| 309 | - |
|
| 310 | - $results[] = array_reduce($users, function (array $carry, IUser $user) use ($restrictGroups) { |
|
| 311 | - // is sharing restricted to groups only? |
|
| 312 | - if ($restrictGroups !== false) { |
|
| 313 | - $userGroups = $this->groupManager->getUserGroupIds($user); |
|
| 314 | - if (count(array_intersect($userGroups, $restrictGroups)) === 0) { |
|
| 315 | - return $carry; |
|
| 316 | - } |
|
| 317 | - } |
|
| 318 | - |
|
| 319 | - $carry[] = $this->principalPrefix . '/' . $user->getUID(); |
|
| 320 | - return $carry; |
|
| 321 | - }, []); |
|
| 322 | - break; |
|
| 323 | - |
|
| 324 | - case '{DAV:}displayname': |
|
| 325 | - |
|
| 326 | - if (!$allowEnumeration) { |
|
| 327 | - if ($allowEnumerationFullMatch) { |
|
| 328 | - $lowerSearch = strtolower($value); |
|
| 329 | - $users = $this->userManager->searchDisplayName($value, $searchLimit); |
|
| 330 | - $users = \array_filter($users, static function (IUser $user) use ($lowerSearch, $ignoreSecondDisplayName) { |
|
| 331 | - $lowerDisplayName = strtolower($user->getDisplayName()); |
|
| 332 | - return $lowerDisplayName === $lowerSearch || ($ignoreSecondDisplayName && trim(preg_replace('/ \(.*\)$/', '', $lowerDisplayName)) === $lowerSearch); |
|
| 333 | - }); |
|
| 334 | - } else { |
|
| 335 | - $users = []; |
|
| 336 | - } |
|
| 337 | - } else { |
|
| 338 | - $users = $this->userManager->searchDisplayName($value, $searchLimit); |
|
| 339 | - $users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $allowEnumerationFullMatch, $currentUserGroups) { |
|
| 340 | - if ($allowEnumerationFullMatch && $user->getDisplayName() === $value) { |
|
| 341 | - return true; |
|
| 342 | - } |
|
| 343 | - |
|
| 344 | - if ($limitEnumerationPhone |
|
| 345 | - && $currentUser instanceof IUser |
|
| 346 | - && $this->knownUserService->isKnownToUser($currentUser->getUID(), $user->getUID())) { |
|
| 347 | - // Synced phonebook match |
|
| 348 | - return true; |
|
| 349 | - } |
|
| 350 | - |
|
| 351 | - if (!$limitEnumerationGroup) { |
|
| 352 | - // No limitation on enumeration, all allowed |
|
| 353 | - return true; |
|
| 354 | - } |
|
| 355 | - |
|
| 356 | - return !empty($currentUserGroups) && !empty(array_intersect( |
|
| 357 | - $this->groupManager->getUserGroupIds($user), |
|
| 358 | - $currentUserGroups |
|
| 359 | - )); |
|
| 360 | - }); |
|
| 361 | - } |
|
| 362 | - |
|
| 363 | - $results[] = array_reduce($users, function (array $carry, IUser $user) use ($restrictGroups) { |
|
| 364 | - // is sharing restricted to groups only? |
|
| 365 | - if ($restrictGroups !== false) { |
|
| 366 | - $userGroups = $this->groupManager->getUserGroupIds($user); |
|
| 367 | - if (count(array_intersect($userGroups, $restrictGroups)) === 0) { |
|
| 368 | - return $carry; |
|
| 369 | - } |
|
| 370 | - } |
|
| 371 | - |
|
| 372 | - $carry[] = $this->principalPrefix . '/' . $user->getUID(); |
|
| 373 | - return $carry; |
|
| 374 | - }, []); |
|
| 375 | - break; |
|
| 376 | - |
|
| 377 | - case '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set': |
|
| 378 | - // If you add support for more search properties that qualify as a user-address, |
|
| 379 | - // please also add them to the array below |
|
| 380 | - $results[] = $this->searchUserPrincipals([ |
|
| 381 | - // In theory this should also search for principal:principals/users/... |
|
| 382 | - // but that's used internally only anyway and i don't know of any client querying that |
|
| 383 | - '{http://sabredav.org/ns}email-address' => $value, |
|
| 384 | - ], 'anyof'); |
|
| 385 | - break; |
|
| 386 | - |
|
| 387 | - default: |
|
| 388 | - $results[] = []; |
|
| 389 | - break; |
|
| 390 | - } |
|
| 391 | - } |
|
| 392 | - |
|
| 393 | - // results is an array of arrays, so this is not the first search result |
|
| 394 | - // but the results of the first searchProperty |
|
| 395 | - if (count($results) === 1) { |
|
| 396 | - return $results[0]; |
|
| 397 | - } |
|
| 398 | - |
|
| 399 | - switch ($test) { |
|
| 400 | - case 'anyof': |
|
| 401 | - return array_values(array_unique(array_merge(...$results))); |
|
| 402 | - |
|
| 403 | - case 'allof': |
|
| 404 | - default: |
|
| 405 | - return array_values(array_intersect(...$results)); |
|
| 406 | - } |
|
| 407 | - } |
|
| 408 | - |
|
| 409 | - /** |
|
| 410 | - * @param string $prefixPath |
|
| 411 | - * @param array $searchProperties |
|
| 412 | - * @param string $test |
|
| 413 | - * @return array |
|
| 414 | - */ |
|
| 415 | - public function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { |
|
| 416 | - if (count($searchProperties) === 0) { |
|
| 417 | - return []; |
|
| 418 | - } |
|
| 419 | - |
|
| 420 | - switch ($prefixPath) { |
|
| 421 | - case 'principals/users': |
|
| 422 | - return $this->searchUserPrincipals($searchProperties, $test); |
|
| 423 | - |
|
| 424 | - default: |
|
| 425 | - return []; |
|
| 426 | - } |
|
| 427 | - } |
|
| 428 | - |
|
| 429 | - /** |
|
| 430 | - * @param string $uri |
|
| 431 | - * @param string $principalPrefix |
|
| 432 | - * @return string |
|
| 433 | - */ |
|
| 434 | - public function findByUri($uri, $principalPrefix) { |
|
| 435 | - // If sharing is disabled, return the empty array |
|
| 436 | - $shareAPIEnabled = $this->shareManager->shareApiEnabled(); |
|
| 437 | - if (!$shareAPIEnabled) { |
|
| 438 | - return null; |
|
| 439 | - } |
|
| 440 | - |
|
| 441 | - // If sharing is restricted to group members only, |
|
| 442 | - // return only members that have groups in common |
|
| 443 | - $restrictGroups = false; |
|
| 444 | - if ($this->shareManager->shareWithGroupMembersOnly()) { |
|
| 445 | - $user = $this->userSession->getUser(); |
|
| 446 | - if (!$user) { |
|
| 447 | - return null; |
|
| 448 | - } |
|
| 449 | - |
|
| 450 | - $restrictGroups = $this->groupManager->getUserGroupIds($user); |
|
| 451 | - } |
|
| 452 | - |
|
| 453 | - if (str_starts_with($uri, 'mailto:')) { |
|
| 454 | - if ($principalPrefix === 'principals/users') { |
|
| 455 | - $users = $this->userManager->getByEmail(substr($uri, 7)); |
|
| 456 | - if (count($users) !== 1) { |
|
| 457 | - return null; |
|
| 458 | - } |
|
| 459 | - $user = $users[0]; |
|
| 460 | - |
|
| 461 | - if ($restrictGroups !== false) { |
|
| 462 | - $userGroups = $this->groupManager->getUserGroupIds($user); |
|
| 463 | - if (count(array_intersect($userGroups, $restrictGroups)) === 0) { |
|
| 464 | - return null; |
|
| 465 | - } |
|
| 466 | - } |
|
| 467 | - |
|
| 468 | - return $this->principalPrefix . '/' . $user->getUID(); |
|
| 469 | - } |
|
| 470 | - } |
|
| 471 | - if (str_starts_with($uri, 'principal:')) { |
|
| 472 | - $principal = substr($uri, 10); |
|
| 473 | - $principal = $this->getPrincipalByPath($principal); |
|
| 474 | - if ($principal !== null) { |
|
| 475 | - return $principal['uri']; |
|
| 476 | - } |
|
| 477 | - } |
|
| 478 | - |
|
| 479 | - return null; |
|
| 480 | - } |
|
| 481 | - |
|
| 482 | - /** |
|
| 483 | - * @param IUser $user |
|
| 484 | - * @param string[]|null $propertyFilter |
|
| 485 | - * @return array |
|
| 486 | - * @throws PropertyDoesNotExistException |
|
| 487 | - */ |
|
| 488 | - protected function userToPrincipal($user, ?array $propertyFilter = null) { |
|
| 489 | - $wantsProperty = static function (string $name) use ($propertyFilter) { |
|
| 490 | - if ($propertyFilter === null) { |
|
| 491 | - return true; |
|
| 492 | - } |
|
| 493 | - |
|
| 494 | - return in_array($name, $propertyFilter, true); |
|
| 495 | - }; |
|
| 496 | - |
|
| 497 | - $userId = $user->getUID(); |
|
| 498 | - $displayName = $user->getDisplayName(); |
|
| 499 | - $principal = [ |
|
| 500 | - 'uri' => $this->principalPrefix . '/' . $userId, |
|
| 501 | - '{DAV:}displayname' => is_null($displayName) ? $userId : $displayName, |
|
| 502 | - '{urn:ietf:params:xml:ns:caldav}calendar-user-type' => 'INDIVIDUAL', |
|
| 503 | - ]; |
|
| 504 | - |
|
| 505 | - if ($wantsProperty('{http://nextcloud.com/ns}language')) { |
|
| 506 | - $principal['{http://nextcloud.com/ns}language'] = $this->languageFactory->getUserLanguage($user); |
|
| 507 | - } |
|
| 508 | - |
|
| 509 | - if ($wantsProperty('{http://sabredav.org/ns}email-address')) { |
|
| 510 | - $email = $user->getSystemEMailAddress(); |
|
| 511 | - if (!empty($email)) { |
|
| 512 | - $principal['{http://sabredav.org/ns}email-address'] = $email; |
|
| 513 | - } |
|
| 514 | - } |
|
| 515 | - |
|
| 516 | - if ($wantsProperty('{DAV:}alternate-URI-set')) { |
|
| 517 | - $account = $this->accountManager->getAccount($user); |
|
| 518 | - $alternativeEmails = array_map(static fn (IAccountProperty $property) => 'mailto:' . $property->getValue(), $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties()); |
|
| 519 | - if (!empty($alternativeEmails)) { |
|
| 520 | - $principal['{DAV:}alternate-URI-set'] = $alternativeEmails; |
|
| 521 | - } |
|
| 522 | - } |
|
| 523 | - |
|
| 524 | - return $principal; |
|
| 525 | - } |
|
| 526 | - |
|
| 527 | - public function getPrincipalPrefix() { |
|
| 528 | - return $this->principalPrefix; |
|
| 529 | - } |
|
| 530 | - |
|
| 531 | - /** |
|
| 532 | - * @param string $circleUniqueId |
|
| 533 | - * @return array|null |
|
| 534 | - */ |
|
| 535 | - protected function circleToPrincipal($circleUniqueId) { |
|
| 536 | - if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) { |
|
| 537 | - return null; |
|
| 538 | - } |
|
| 539 | - |
|
| 540 | - try { |
|
| 541 | - $circle = Circles::detailsCircle($circleUniqueId, true); |
|
| 542 | - } catch (ContainerExceptionInterface $ex) { |
|
| 543 | - return null; |
|
| 544 | - } catch (CircleNotFoundException $ex) { |
|
| 545 | - return null; |
|
| 546 | - } |
|
| 547 | - |
|
| 548 | - if (!$circle) { |
|
| 549 | - return null; |
|
| 550 | - } |
|
| 551 | - |
|
| 552 | - $principal = [ |
|
| 553 | - 'uri' => 'principals/circles/' . $circleUniqueId, |
|
| 554 | - '{DAV:}displayname' => $circle->getDisplayName(), |
|
| 555 | - ]; |
|
| 556 | - |
|
| 557 | - return $principal; |
|
| 558 | - } |
|
| 559 | - |
|
| 560 | - /** |
|
| 561 | - * Returns the list of circles a principal is a member of |
|
| 562 | - * |
|
| 563 | - * @param string $principal |
|
| 564 | - * @return array |
|
| 565 | - * @throws Exception |
|
| 566 | - * @throws ContainerExceptionInterface |
|
| 567 | - * @suppress PhanUndeclaredClassMethod |
|
| 568 | - */ |
|
| 569 | - public function getCircleMembership($principal):array { |
|
| 570 | - if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) { |
|
| 571 | - return []; |
|
| 572 | - } |
|
| 573 | - |
|
| 574 | - [$prefix, $name] = \Sabre\Uri\split($principal); |
|
| 575 | - if ($this->hasCircles && $prefix === $this->principalPrefix) { |
|
| 576 | - $user = $this->userManager->get($name); |
|
| 577 | - if (!$user) { |
|
| 578 | - throw new Exception('Principal not found'); |
|
| 579 | - } |
|
| 580 | - |
|
| 581 | - $circles = Circles::joinedCircles($name, true); |
|
| 582 | - |
|
| 583 | - $circles = array_map(function ($circle) { |
|
| 584 | - /** @var Circle $circle */ |
|
| 585 | - return 'principals/circles/' . urlencode($circle->getSingleId()); |
|
| 586 | - }, $circles); |
|
| 587 | - |
|
| 588 | - return $circles; |
|
| 589 | - } |
|
| 590 | - |
|
| 591 | - return []; |
|
| 592 | - } |
|
| 593 | - |
|
| 594 | - /** |
|
| 595 | - * Get all email addresses associated to a principal. |
|
| 596 | - * |
|
| 597 | - * @param array $principal Data from getPrincipal*() |
|
| 598 | - * @return string[] All email addresses without the mailto: prefix |
|
| 599 | - */ |
|
| 600 | - public function getEmailAddressesOfPrincipal(array $principal): array { |
|
| 601 | - $emailAddresses = []; |
|
| 602 | - |
|
| 603 | - if (isset($principal['{http://sabredav.org/ns}email-address'])) { |
|
| 604 | - $emailAddresses[] = $principal['{http://sabredav.org/ns}email-address']; |
|
| 605 | - } |
|
| 606 | - |
|
| 607 | - if (isset($principal['{DAV:}alternate-URI-set'])) { |
|
| 608 | - foreach ($principal['{DAV:}alternate-URI-set'] as $address) { |
|
| 609 | - if (str_starts_with($address, 'mailto:')) { |
|
| 610 | - $emailAddresses[] = substr($address, 7); |
|
| 611 | - } |
|
| 612 | - } |
|
| 613 | - } |
|
| 614 | - |
|
| 615 | - if (isset($principal['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'])) { |
|
| 616 | - foreach ($principal['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'] as $address) { |
|
| 617 | - if (str_starts_with($address, 'mailto:')) { |
|
| 618 | - $emailAddresses[] = substr($address, 7); |
|
| 619 | - } |
|
| 620 | - } |
|
| 621 | - } |
|
| 622 | - |
|
| 623 | - if (isset($principal['{http://calendarserver.org/ns/}email-address-set'])) { |
|
| 624 | - foreach ($principal['{http://calendarserver.org/ns/}email-address-set'] as $address) { |
|
| 625 | - if (str_starts_with($address, 'mailto:')) { |
|
| 626 | - $emailAddresses[] = substr($address, 7); |
|
| 627 | - } |
|
| 628 | - } |
|
| 629 | - } |
|
| 630 | - |
|
| 631 | - return array_values(array_unique($emailAddresses)); |
|
| 632 | - } |
|
| 36 | + /** @var string */ |
|
| 37 | + private $principalPrefix; |
|
| 38 | + |
|
| 39 | + /** @var bool */ |
|
| 40 | + private $hasGroups; |
|
| 41 | + |
|
| 42 | + /** @var bool */ |
|
| 43 | + private $hasCircles; |
|
| 44 | + |
|
| 45 | + /** @var KnownUserService */ |
|
| 46 | + private $knownUserService; |
|
| 47 | + |
|
| 48 | + public function __construct( |
|
| 49 | + private IUserManager $userManager, |
|
| 50 | + private IGroupManager $groupManager, |
|
| 51 | + private IAccountManager $accountManager, |
|
| 52 | + private IShareManager $shareManager, |
|
| 53 | + private IUserSession $userSession, |
|
| 54 | + private IAppManager $appManager, |
|
| 55 | + private ProxyMapper $proxyMapper, |
|
| 56 | + KnownUserService $knownUserService, |
|
| 57 | + private IConfig $config, |
|
| 58 | + private IFactory $languageFactory, |
|
| 59 | + string $principalPrefix = 'principals/users/', |
|
| 60 | + ) { |
|
| 61 | + $this->principalPrefix = trim($principalPrefix, '/'); |
|
| 62 | + $this->hasGroups = $this->hasCircles = ($principalPrefix === 'principals/users/'); |
|
| 63 | + $this->knownUserService = $knownUserService; |
|
| 64 | + } |
|
| 65 | + |
|
| 66 | + use PrincipalProxyTrait { |
|
| 67 | + getGroupMembership as protected traitGetGroupMembership; |
|
| 68 | + } |
|
| 69 | + |
|
| 70 | + /** |
|
| 71 | + * Returns a list of principals based on a prefix. |
|
| 72 | + * |
|
| 73 | + * This prefix will often contain something like 'principals'. You are only |
|
| 74 | + * expected to return principals that are in this base path. |
|
| 75 | + * |
|
| 76 | + * You are expected to return at least a 'uri' for every user, you can |
|
| 77 | + * return any additional properties if you wish so. Common properties are: |
|
| 78 | + * {DAV:}displayname |
|
| 79 | + * |
|
| 80 | + * @param string $prefixPath |
|
| 81 | + * @return string[] |
|
| 82 | + */ |
|
| 83 | + public function getPrincipalsByPrefix($prefixPath) { |
|
| 84 | + $principals = []; |
|
| 85 | + |
|
| 86 | + if ($prefixPath === $this->principalPrefix) { |
|
| 87 | + foreach ($this->userManager->search('') as $user) { |
|
| 88 | + $principals[] = $this->userToPrincipal($user); |
|
| 89 | + } |
|
| 90 | + } |
|
| 91 | + |
|
| 92 | + return $principals; |
|
| 93 | + } |
|
| 94 | + |
|
| 95 | + /** |
|
| 96 | + * Returns a specific principal, specified by it's path. |
|
| 97 | + * The returned structure should be the exact same as from |
|
| 98 | + * getPrincipalsByPrefix. |
|
| 99 | + * |
|
| 100 | + * @param string $path |
|
| 101 | + * @return array |
|
| 102 | + */ |
|
| 103 | + public function getPrincipalByPath($path) { |
|
| 104 | + return $this->getPrincipalPropertiesByPath($path); |
|
| 105 | + } |
|
| 106 | + |
|
| 107 | + /** |
|
| 108 | + * Returns a specific principal, specified by its path. |
|
| 109 | + * The returned structure should be the exact same as from |
|
| 110 | + * getPrincipalsByPrefix. |
|
| 111 | + * |
|
| 112 | + * It is possible to optionally filter retrieved properties in case only a limited set is |
|
| 113 | + * required. Note that the implementation might return more properties than requested. |
|
| 114 | + * |
|
| 115 | + * @param string $path The path of the principal |
|
| 116 | + * @param string[]|null $propertyFilter A list of properties to be retrieved or all if null. An empty array will cause a very shallow principal to be retrieved. |
|
| 117 | + */ |
|
| 118 | + public function getPrincipalPropertiesByPath($path, ?array $propertyFilter = null): ?array { |
|
| 119 | + [$prefix, $name] = \Sabre\Uri\split($path); |
|
| 120 | + $decodedName = urldecode($name); |
|
| 121 | + |
|
| 122 | + if ($name === 'calendar-proxy-write' || $name === 'calendar-proxy-read') { |
|
| 123 | + [$prefix2, $name2] = \Sabre\Uri\split($prefix); |
|
| 124 | + |
|
| 125 | + if ($prefix2 === $this->principalPrefix) { |
|
| 126 | + $user = $this->userManager->get($name2); |
|
| 127 | + |
|
| 128 | + if ($user !== null) { |
|
| 129 | + return [ |
|
| 130 | + 'uri' => 'principals/users/' . $user->getUID() . '/' . $name, |
|
| 131 | + ]; |
|
| 132 | + } |
|
| 133 | + return null; |
|
| 134 | + } |
|
| 135 | + } |
|
| 136 | + |
|
| 137 | + if ($prefix === $this->principalPrefix) { |
|
| 138 | + // Depending on where it is called, it may happen that this function |
|
| 139 | + // is called either with a urlencoded version of the name or with a non-urlencoded one. |
|
| 140 | + // The urldecode function replaces %## and +, both of which are forbidden in usernames. |
|
| 141 | + // Hence there can be no ambiguity here and it is safe to call urldecode on all usernames |
|
| 142 | + $user = $this->userManager->get($decodedName); |
|
| 143 | + |
|
| 144 | + if ($user !== null) { |
|
| 145 | + return $this->userToPrincipal($user, $propertyFilter); |
|
| 146 | + } |
|
| 147 | + } elseif ($prefix === 'principals/circles') { |
|
| 148 | + if ($this->userSession->getUser() !== null) { |
|
| 149 | + // At the time of writing - 2021-01-19 — a mixed state is possible. |
|
| 150 | + // The second condition can be removed when this is fixed. |
|
| 151 | + return $this->circleToPrincipal($decodedName) |
|
| 152 | + ?: $this->circleToPrincipal($name); |
|
| 153 | + } |
|
| 154 | + } elseif ($prefix === 'principals/groups') { |
|
| 155 | + // At the time of writing - 2021-01-19 — a mixed state is possible. |
|
| 156 | + // The second condition can be removed when this is fixed. |
|
| 157 | + $group = $this->groupManager->get($decodedName) |
|
| 158 | + ?: $this->groupManager->get($name); |
|
| 159 | + if ($group instanceof IGroup) { |
|
| 160 | + return [ |
|
| 161 | + 'uri' => 'principals/groups/' . $name, |
|
| 162 | + '{DAV:}displayname' => $group->getDisplayName(), |
|
| 163 | + ]; |
|
| 164 | + } |
|
| 165 | + } elseif ($prefix === 'principals/system') { |
|
| 166 | + return [ |
|
| 167 | + 'uri' => 'principals/system/' . $name, |
|
| 168 | + '{DAV:}displayname' => $this->languageFactory->get('dav')->t('Accounts'), |
|
| 169 | + ]; |
|
| 170 | + } elseif ($prefix === 'principals/shares') { |
|
| 171 | + return [ |
|
| 172 | + 'uri' => 'principals/shares/' . $name, |
|
| 173 | + '{DAV:}displayname' => $name, |
|
| 174 | + ]; |
|
| 175 | + } |
|
| 176 | + return null; |
|
| 177 | + } |
|
| 178 | + |
|
| 179 | + /** |
|
| 180 | + * Returns the list of groups a principal is a member of |
|
| 181 | + * |
|
| 182 | + * @param string $principal |
|
| 183 | + * @param bool $needGroups |
|
| 184 | + * @return array |
|
| 185 | + * @throws Exception |
|
| 186 | + */ |
|
| 187 | + public function getGroupMembership($principal, $needGroups = false) { |
|
| 188 | + [$prefix, $name] = \Sabre\Uri\split($principal); |
|
| 189 | + |
|
| 190 | + if ($prefix !== $this->principalPrefix) { |
|
| 191 | + return []; |
|
| 192 | + } |
|
| 193 | + |
|
| 194 | + $user = $this->userManager->get($name); |
|
| 195 | + if (!$user) { |
|
| 196 | + throw new Exception('Principal not found'); |
|
| 197 | + } |
|
| 198 | + |
|
| 199 | + $groups = []; |
|
| 200 | + |
|
| 201 | + if ($this->hasGroups || $needGroups) { |
|
| 202 | + $userGroups = $this->groupManager->getUserGroups($user); |
|
| 203 | + foreach ($userGroups as $userGroup) { |
|
| 204 | + if ($userGroup->hideFromCollaboration()) { |
|
| 205 | + continue; |
|
| 206 | + } |
|
| 207 | + $groups[] = 'principals/groups/' . urlencode($userGroup->getGID()); |
|
| 208 | + } |
|
| 209 | + } |
|
| 210 | + |
|
| 211 | + $groups = array_unique(array_merge( |
|
| 212 | + $groups, |
|
| 213 | + $this->traitGetGroupMembership($principal, $needGroups) |
|
| 214 | + )); |
|
| 215 | + |
|
| 216 | + return $groups; |
|
| 217 | + } |
|
| 218 | + |
|
| 219 | + /** |
|
| 220 | + * @param string $path |
|
| 221 | + * @param PropPatch $propPatch |
|
| 222 | + * @return int |
|
| 223 | + */ |
|
| 224 | + public function updatePrincipal($path, PropPatch $propPatch) { |
|
| 225 | + // Updating schedule-default-calendar-URL is handled in CustomPropertiesBackend |
|
| 226 | + return 0; |
|
| 227 | + } |
|
| 228 | + |
|
| 229 | + /** |
|
| 230 | + * Search user principals |
|
| 231 | + * |
|
| 232 | + * @param array $searchProperties |
|
| 233 | + * @param string $test |
|
| 234 | + * @return array |
|
| 235 | + */ |
|
| 236 | + protected function searchUserPrincipals(array $searchProperties, $test = 'allof') { |
|
| 237 | + $results = []; |
|
| 238 | + |
|
| 239 | + // If sharing is disabled, return the empty array |
|
| 240 | + $shareAPIEnabled = $this->shareManager->shareApiEnabled(); |
|
| 241 | + if (!$shareAPIEnabled) { |
|
| 242 | + return []; |
|
| 243 | + } |
|
| 244 | + |
|
| 245 | + $allowEnumeration = $this->shareManager->allowEnumeration(); |
|
| 246 | + $limitEnumerationGroup = $this->shareManager->limitEnumerationToGroups(); |
|
| 247 | + $limitEnumerationPhone = $this->shareManager->limitEnumerationToPhone(); |
|
| 248 | + $allowEnumerationFullMatch = $this->shareManager->allowEnumerationFullMatch(); |
|
| 249 | + $ignoreSecondDisplayName = $this->shareManager->ignoreSecondDisplayName(); |
|
| 250 | + $matchEmail = $this->shareManager->matchEmail(); |
|
| 251 | + |
|
| 252 | + // If sharing is restricted to group members only, |
|
| 253 | + // return only members that have groups in common |
|
| 254 | + $restrictGroups = false; |
|
| 255 | + $currentUser = $this->userSession->getUser(); |
|
| 256 | + if ($this->shareManager->shareWithGroupMembersOnly()) { |
|
| 257 | + if (!$currentUser instanceof IUser) { |
|
| 258 | + return []; |
|
| 259 | + } |
|
| 260 | + |
|
| 261 | + $restrictGroups = $this->groupManager->getUserGroupIds($currentUser); |
|
| 262 | + } |
|
| 263 | + |
|
| 264 | + $currentUserGroups = []; |
|
| 265 | + if ($limitEnumerationGroup) { |
|
| 266 | + if ($currentUser instanceof IUser) { |
|
| 267 | + $currentUserGroups = $this->groupManager->getUserGroupIds($currentUser); |
|
| 268 | + } |
|
| 269 | + } |
|
| 270 | + |
|
| 271 | + $searchLimit = $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT); |
|
| 272 | + if ($searchLimit <= 0) { |
|
| 273 | + $searchLimit = null; |
|
| 274 | + } |
|
| 275 | + foreach ($searchProperties as $prop => $value) { |
|
| 276 | + switch ($prop) { |
|
| 277 | + case '{http://sabredav.org/ns}email-address': |
|
| 278 | + if (!$allowEnumeration) { |
|
| 279 | + if ($allowEnumerationFullMatch && $matchEmail) { |
|
| 280 | + $users = $this->userManager->getByEmail($value); |
|
| 281 | + } else { |
|
| 282 | + $users = []; |
|
| 283 | + } |
|
| 284 | + } else { |
|
| 285 | + $users = $this->userManager->getByEmail($value); |
|
| 286 | + $users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $allowEnumerationFullMatch, $currentUserGroups) { |
|
| 287 | + if ($allowEnumerationFullMatch && $user->getSystemEMailAddress() === $value) { |
|
| 288 | + return true; |
|
| 289 | + } |
|
| 290 | + |
|
| 291 | + if ($limitEnumerationPhone |
|
| 292 | + && $currentUser instanceof IUser |
|
| 293 | + && $this->knownUserService->isKnownToUser($currentUser->getUID(), $user->getUID())) { |
|
| 294 | + // Synced phonebook match |
|
| 295 | + return true; |
|
| 296 | + } |
|
| 297 | + |
|
| 298 | + if (!$limitEnumerationGroup) { |
|
| 299 | + // No limitation on enumeration, all allowed |
|
| 300 | + return true; |
|
| 301 | + } |
|
| 302 | + |
|
| 303 | + return !empty($currentUserGroups) && !empty(array_intersect( |
|
| 304 | + $this->groupManager->getUserGroupIds($user), |
|
| 305 | + $currentUserGroups |
|
| 306 | + )); |
|
| 307 | + }); |
|
| 308 | + } |
|
| 309 | + |
|
| 310 | + $results[] = array_reduce($users, function (array $carry, IUser $user) use ($restrictGroups) { |
|
| 311 | + // is sharing restricted to groups only? |
|
| 312 | + if ($restrictGroups !== false) { |
|
| 313 | + $userGroups = $this->groupManager->getUserGroupIds($user); |
|
| 314 | + if (count(array_intersect($userGroups, $restrictGroups)) === 0) { |
|
| 315 | + return $carry; |
|
| 316 | + } |
|
| 317 | + } |
|
| 318 | + |
|
| 319 | + $carry[] = $this->principalPrefix . '/' . $user->getUID(); |
|
| 320 | + return $carry; |
|
| 321 | + }, []); |
|
| 322 | + break; |
|
| 323 | + |
|
| 324 | + case '{DAV:}displayname': |
|
| 325 | + |
|
| 326 | + if (!$allowEnumeration) { |
|
| 327 | + if ($allowEnumerationFullMatch) { |
|
| 328 | + $lowerSearch = strtolower($value); |
|
| 329 | + $users = $this->userManager->searchDisplayName($value, $searchLimit); |
|
| 330 | + $users = \array_filter($users, static function (IUser $user) use ($lowerSearch, $ignoreSecondDisplayName) { |
|
| 331 | + $lowerDisplayName = strtolower($user->getDisplayName()); |
|
| 332 | + return $lowerDisplayName === $lowerSearch || ($ignoreSecondDisplayName && trim(preg_replace('/ \(.*\)$/', '', $lowerDisplayName)) === $lowerSearch); |
|
| 333 | + }); |
|
| 334 | + } else { |
|
| 335 | + $users = []; |
|
| 336 | + } |
|
| 337 | + } else { |
|
| 338 | + $users = $this->userManager->searchDisplayName($value, $searchLimit); |
|
| 339 | + $users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $allowEnumerationFullMatch, $currentUserGroups) { |
|
| 340 | + if ($allowEnumerationFullMatch && $user->getDisplayName() === $value) { |
|
| 341 | + return true; |
|
| 342 | + } |
|
| 343 | + |
|
| 344 | + if ($limitEnumerationPhone |
|
| 345 | + && $currentUser instanceof IUser |
|
| 346 | + && $this->knownUserService->isKnownToUser($currentUser->getUID(), $user->getUID())) { |
|
| 347 | + // Synced phonebook match |
|
| 348 | + return true; |
|
| 349 | + } |
|
| 350 | + |
|
| 351 | + if (!$limitEnumerationGroup) { |
|
| 352 | + // No limitation on enumeration, all allowed |
|
| 353 | + return true; |
|
| 354 | + } |
|
| 355 | + |
|
| 356 | + return !empty($currentUserGroups) && !empty(array_intersect( |
|
| 357 | + $this->groupManager->getUserGroupIds($user), |
|
| 358 | + $currentUserGroups |
|
| 359 | + )); |
|
| 360 | + }); |
|
| 361 | + } |
|
| 362 | + |
|
| 363 | + $results[] = array_reduce($users, function (array $carry, IUser $user) use ($restrictGroups) { |
|
| 364 | + // is sharing restricted to groups only? |
|
| 365 | + if ($restrictGroups !== false) { |
|
| 366 | + $userGroups = $this->groupManager->getUserGroupIds($user); |
|
| 367 | + if (count(array_intersect($userGroups, $restrictGroups)) === 0) { |
|
| 368 | + return $carry; |
|
| 369 | + } |
|
| 370 | + } |
|
| 371 | + |
|
| 372 | + $carry[] = $this->principalPrefix . '/' . $user->getUID(); |
|
| 373 | + return $carry; |
|
| 374 | + }, []); |
|
| 375 | + break; |
|
| 376 | + |
|
| 377 | + case '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set': |
|
| 378 | + // If you add support for more search properties that qualify as a user-address, |
|
| 379 | + // please also add them to the array below |
|
| 380 | + $results[] = $this->searchUserPrincipals([ |
|
| 381 | + // In theory this should also search for principal:principals/users/... |
|
| 382 | + // but that's used internally only anyway and i don't know of any client querying that |
|
| 383 | + '{http://sabredav.org/ns}email-address' => $value, |
|
| 384 | + ], 'anyof'); |
|
| 385 | + break; |
|
| 386 | + |
|
| 387 | + default: |
|
| 388 | + $results[] = []; |
|
| 389 | + break; |
|
| 390 | + } |
|
| 391 | + } |
|
| 392 | + |
|
| 393 | + // results is an array of arrays, so this is not the first search result |
|
| 394 | + // but the results of the first searchProperty |
|
| 395 | + if (count($results) === 1) { |
|
| 396 | + return $results[0]; |
|
| 397 | + } |
|
| 398 | + |
|
| 399 | + switch ($test) { |
|
| 400 | + case 'anyof': |
|
| 401 | + return array_values(array_unique(array_merge(...$results))); |
|
| 402 | + |
|
| 403 | + case 'allof': |
|
| 404 | + default: |
|
| 405 | + return array_values(array_intersect(...$results)); |
|
| 406 | + } |
|
| 407 | + } |
|
| 408 | + |
|
| 409 | + /** |
|
| 410 | + * @param string $prefixPath |
|
| 411 | + * @param array $searchProperties |
|
| 412 | + * @param string $test |
|
| 413 | + * @return array |
|
| 414 | + */ |
|
| 415 | + public function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { |
|
| 416 | + if (count($searchProperties) === 0) { |
|
| 417 | + return []; |
|
| 418 | + } |
|
| 419 | + |
|
| 420 | + switch ($prefixPath) { |
|
| 421 | + case 'principals/users': |
|
| 422 | + return $this->searchUserPrincipals($searchProperties, $test); |
|
| 423 | + |
|
| 424 | + default: |
|
| 425 | + return []; |
|
| 426 | + } |
|
| 427 | + } |
|
| 428 | + |
|
| 429 | + /** |
|
| 430 | + * @param string $uri |
|
| 431 | + * @param string $principalPrefix |
|
| 432 | + * @return string |
|
| 433 | + */ |
|
| 434 | + public function findByUri($uri, $principalPrefix) { |
|
| 435 | + // If sharing is disabled, return the empty array |
|
| 436 | + $shareAPIEnabled = $this->shareManager->shareApiEnabled(); |
|
| 437 | + if (!$shareAPIEnabled) { |
|
| 438 | + return null; |
|
| 439 | + } |
|
| 440 | + |
|
| 441 | + // If sharing is restricted to group members only, |
|
| 442 | + // return only members that have groups in common |
|
| 443 | + $restrictGroups = false; |
|
| 444 | + if ($this->shareManager->shareWithGroupMembersOnly()) { |
|
| 445 | + $user = $this->userSession->getUser(); |
|
| 446 | + if (!$user) { |
|
| 447 | + return null; |
|
| 448 | + } |
|
| 449 | + |
|
| 450 | + $restrictGroups = $this->groupManager->getUserGroupIds($user); |
|
| 451 | + } |
|
| 452 | + |
|
| 453 | + if (str_starts_with($uri, 'mailto:')) { |
|
| 454 | + if ($principalPrefix === 'principals/users') { |
|
| 455 | + $users = $this->userManager->getByEmail(substr($uri, 7)); |
|
| 456 | + if (count($users) !== 1) { |
|
| 457 | + return null; |
|
| 458 | + } |
|
| 459 | + $user = $users[0]; |
|
| 460 | + |
|
| 461 | + if ($restrictGroups !== false) { |
|
| 462 | + $userGroups = $this->groupManager->getUserGroupIds($user); |
|
| 463 | + if (count(array_intersect($userGroups, $restrictGroups)) === 0) { |
|
| 464 | + return null; |
|
| 465 | + } |
|
| 466 | + } |
|
| 467 | + |
|
| 468 | + return $this->principalPrefix . '/' . $user->getUID(); |
|
| 469 | + } |
|
| 470 | + } |
|
| 471 | + if (str_starts_with($uri, 'principal:')) { |
|
| 472 | + $principal = substr($uri, 10); |
|
| 473 | + $principal = $this->getPrincipalByPath($principal); |
|
| 474 | + if ($principal !== null) { |
|
| 475 | + return $principal['uri']; |
|
| 476 | + } |
|
| 477 | + } |
|
| 478 | + |
|
| 479 | + return null; |
|
| 480 | + } |
|
| 481 | + |
|
| 482 | + /** |
|
| 483 | + * @param IUser $user |
|
| 484 | + * @param string[]|null $propertyFilter |
|
| 485 | + * @return array |
|
| 486 | + * @throws PropertyDoesNotExistException |
|
| 487 | + */ |
|
| 488 | + protected function userToPrincipal($user, ?array $propertyFilter = null) { |
|
| 489 | + $wantsProperty = static function (string $name) use ($propertyFilter) { |
|
| 490 | + if ($propertyFilter === null) { |
|
| 491 | + return true; |
|
| 492 | + } |
|
| 493 | + |
|
| 494 | + return in_array($name, $propertyFilter, true); |
|
| 495 | + }; |
|
| 496 | + |
|
| 497 | + $userId = $user->getUID(); |
|
| 498 | + $displayName = $user->getDisplayName(); |
|
| 499 | + $principal = [ |
|
| 500 | + 'uri' => $this->principalPrefix . '/' . $userId, |
|
| 501 | + '{DAV:}displayname' => is_null($displayName) ? $userId : $displayName, |
|
| 502 | + '{urn:ietf:params:xml:ns:caldav}calendar-user-type' => 'INDIVIDUAL', |
|
| 503 | + ]; |
|
| 504 | + |
|
| 505 | + if ($wantsProperty('{http://nextcloud.com/ns}language')) { |
|
| 506 | + $principal['{http://nextcloud.com/ns}language'] = $this->languageFactory->getUserLanguage($user); |
|
| 507 | + } |
|
| 508 | + |
|
| 509 | + if ($wantsProperty('{http://sabredav.org/ns}email-address')) { |
|
| 510 | + $email = $user->getSystemEMailAddress(); |
|
| 511 | + if (!empty($email)) { |
|
| 512 | + $principal['{http://sabredav.org/ns}email-address'] = $email; |
|
| 513 | + } |
|
| 514 | + } |
|
| 515 | + |
|
| 516 | + if ($wantsProperty('{DAV:}alternate-URI-set')) { |
|
| 517 | + $account = $this->accountManager->getAccount($user); |
|
| 518 | + $alternativeEmails = array_map(static fn (IAccountProperty $property) => 'mailto:' . $property->getValue(), $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties()); |
|
| 519 | + if (!empty($alternativeEmails)) { |
|
| 520 | + $principal['{DAV:}alternate-URI-set'] = $alternativeEmails; |
|
| 521 | + } |
|
| 522 | + } |
|
| 523 | + |
|
| 524 | + return $principal; |
|
| 525 | + } |
|
| 526 | + |
|
| 527 | + public function getPrincipalPrefix() { |
|
| 528 | + return $this->principalPrefix; |
|
| 529 | + } |
|
| 530 | + |
|
| 531 | + /** |
|
| 532 | + * @param string $circleUniqueId |
|
| 533 | + * @return array|null |
|
| 534 | + */ |
|
| 535 | + protected function circleToPrincipal($circleUniqueId) { |
|
| 536 | + if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) { |
|
| 537 | + return null; |
|
| 538 | + } |
|
| 539 | + |
|
| 540 | + try { |
|
| 541 | + $circle = Circles::detailsCircle($circleUniqueId, true); |
|
| 542 | + } catch (ContainerExceptionInterface $ex) { |
|
| 543 | + return null; |
|
| 544 | + } catch (CircleNotFoundException $ex) { |
|
| 545 | + return null; |
|
| 546 | + } |
|
| 547 | + |
|
| 548 | + if (!$circle) { |
|
| 549 | + return null; |
|
| 550 | + } |
|
| 551 | + |
|
| 552 | + $principal = [ |
|
| 553 | + 'uri' => 'principals/circles/' . $circleUniqueId, |
|
| 554 | + '{DAV:}displayname' => $circle->getDisplayName(), |
|
| 555 | + ]; |
|
| 556 | + |
|
| 557 | + return $principal; |
|
| 558 | + } |
|
| 559 | + |
|
| 560 | + /** |
|
| 561 | + * Returns the list of circles a principal is a member of |
|
| 562 | + * |
|
| 563 | + * @param string $principal |
|
| 564 | + * @return array |
|
| 565 | + * @throws Exception |
|
| 566 | + * @throws ContainerExceptionInterface |
|
| 567 | + * @suppress PhanUndeclaredClassMethod |
|
| 568 | + */ |
|
| 569 | + public function getCircleMembership($principal):array { |
|
| 570 | + if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) { |
|
| 571 | + return []; |
|
| 572 | + } |
|
| 573 | + |
|
| 574 | + [$prefix, $name] = \Sabre\Uri\split($principal); |
|
| 575 | + if ($this->hasCircles && $prefix === $this->principalPrefix) { |
|
| 576 | + $user = $this->userManager->get($name); |
|
| 577 | + if (!$user) { |
|
| 578 | + throw new Exception('Principal not found'); |
|
| 579 | + } |
|
| 580 | + |
|
| 581 | + $circles = Circles::joinedCircles($name, true); |
|
| 582 | + |
|
| 583 | + $circles = array_map(function ($circle) { |
|
| 584 | + /** @var Circle $circle */ |
|
| 585 | + return 'principals/circles/' . urlencode($circle->getSingleId()); |
|
| 586 | + }, $circles); |
|
| 587 | + |
|
| 588 | + return $circles; |
|
| 589 | + } |
|
| 590 | + |
|
| 591 | + return []; |
|
| 592 | + } |
|
| 593 | + |
|
| 594 | + /** |
|
| 595 | + * Get all email addresses associated to a principal. |
|
| 596 | + * |
|
| 597 | + * @param array $principal Data from getPrincipal*() |
|
| 598 | + * @return string[] All email addresses without the mailto: prefix |
|
| 599 | + */ |
|
| 600 | + public function getEmailAddressesOfPrincipal(array $principal): array { |
|
| 601 | + $emailAddresses = []; |
|
| 602 | + |
|
| 603 | + if (isset($principal['{http://sabredav.org/ns}email-address'])) { |
|
| 604 | + $emailAddresses[] = $principal['{http://sabredav.org/ns}email-address']; |
|
| 605 | + } |
|
| 606 | + |
|
| 607 | + if (isset($principal['{DAV:}alternate-URI-set'])) { |
|
| 608 | + foreach ($principal['{DAV:}alternate-URI-set'] as $address) { |
|
| 609 | + if (str_starts_with($address, 'mailto:')) { |
|
| 610 | + $emailAddresses[] = substr($address, 7); |
|
| 611 | + } |
|
| 612 | + } |
|
| 613 | + } |
|
| 614 | + |
|
| 615 | + if (isset($principal['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'])) { |
|
| 616 | + foreach ($principal['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'] as $address) { |
|
| 617 | + if (str_starts_with($address, 'mailto:')) { |
|
| 618 | + $emailAddresses[] = substr($address, 7); |
|
| 619 | + } |
|
| 620 | + } |
|
| 621 | + } |
|
| 622 | + |
|
| 623 | + if (isset($principal['{http://calendarserver.org/ns/}email-address-set'])) { |
|
| 624 | + foreach ($principal['{http://calendarserver.org/ns/}email-address-set'] as $address) { |
|
| 625 | + if (str_starts_with($address, 'mailto:')) { |
|
| 626 | + $emailAddresses[] = substr($address, 7); |
|
| 627 | + } |
|
| 628 | + } |
|
| 629 | + } |
|
| 630 | + |
|
| 631 | + return array_values(array_unique($emailAddresses)); |
|
| 632 | + } |
|
| 633 | 633 | } |
@@ -27,272 +27,272 @@ |
||
| 27 | 27 | */ |
| 28 | 28 | class PluginManager { |
| 29 | 29 | |
| 30 | - /** |
|
| 31 | - * App plugins |
|
| 32 | - * |
|
| 33 | - * @var ServerPlugin[] |
|
| 34 | - */ |
|
| 35 | - private $plugins = []; |
|
| 36 | - |
|
| 37 | - /** |
|
| 38 | - * App collections |
|
| 39 | - * |
|
| 40 | - * @var Collection[] |
|
| 41 | - */ |
|
| 42 | - private $collections = []; |
|
| 43 | - |
|
| 44 | - /** |
|
| 45 | - * Address book plugins |
|
| 46 | - * |
|
| 47 | - * @var IAddressBookProvider[] |
|
| 48 | - */ |
|
| 49 | - private $addressBookPlugins = []; |
|
| 50 | - |
|
| 51 | - /** |
|
| 52 | - * Calendar plugins |
|
| 53 | - * |
|
| 54 | - * @var ICalendarProvider[] |
|
| 55 | - */ |
|
| 56 | - private $calendarPlugins = []; |
|
| 57 | - |
|
| 58 | - /** @var bool */ |
|
| 59 | - private $populated = false; |
|
| 60 | - |
|
| 61 | - /** |
|
| 62 | - * Contstruct a PluginManager |
|
| 63 | - * |
|
| 64 | - * @param ServerContainer $container server container for resolving plugin classes |
|
| 65 | - * @param IAppManager $appManager app manager to loading apps and their info |
|
| 66 | - */ |
|
| 67 | - public function __construct( |
|
| 68 | - private ServerContainer $container, |
|
| 69 | - private IAppManager $appManager, |
|
| 70 | - ) { |
|
| 71 | - } |
|
| 72 | - |
|
| 73 | - /** |
|
| 74 | - * Returns an array of app-registered plugins |
|
| 75 | - * |
|
| 76 | - * @return ServerPlugin[] |
|
| 77 | - */ |
|
| 78 | - public function getAppPlugins() { |
|
| 79 | - $this->populate(); |
|
| 80 | - return $this->plugins; |
|
| 81 | - } |
|
| 82 | - |
|
| 83 | - /** |
|
| 84 | - * Returns an array of app-registered collections |
|
| 85 | - * |
|
| 86 | - * @return array |
|
| 87 | - */ |
|
| 88 | - public function getAppCollections() { |
|
| 89 | - $this->populate(); |
|
| 90 | - return $this->collections; |
|
| 91 | - } |
|
| 92 | - |
|
| 93 | - /** |
|
| 94 | - * @return IAddressBookProvider[] |
|
| 95 | - */ |
|
| 96 | - public function getAddressBookPlugins(): array { |
|
| 97 | - $this->populate(); |
|
| 98 | - return $this->addressBookPlugins; |
|
| 99 | - } |
|
| 100 | - |
|
| 101 | - /** |
|
| 102 | - * Returns an array of app-registered calendar plugins |
|
| 103 | - * |
|
| 104 | - * @return ICalendarProvider[] |
|
| 105 | - */ |
|
| 106 | - public function getCalendarPlugins():array { |
|
| 107 | - $this->populate(); |
|
| 108 | - return $this->calendarPlugins; |
|
| 109 | - } |
|
| 110 | - |
|
| 111 | - /** |
|
| 112 | - * Retrieve plugin and collection list and populate attributes |
|
| 113 | - */ |
|
| 114 | - private function populate(): void { |
|
| 115 | - if ($this->populated) { |
|
| 116 | - return; |
|
| 117 | - } |
|
| 118 | - $this->populated = true; |
|
| 119 | - |
|
| 120 | - $this->calendarPlugins[] = $this->container->get(AppCalendarPlugin::class); |
|
| 121 | - |
|
| 122 | - foreach ($this->appManager->getEnabledApps() as $app) { |
|
| 123 | - // load plugins and collections from info.xml |
|
| 124 | - $info = $this->appManager->getAppInfo($app); |
|
| 125 | - if (!isset($info['types']) || !in_array('dav', $info['types'], true)) { |
|
| 126 | - continue; |
|
| 127 | - } |
|
| 128 | - $plugins = $this->loadSabrePluginsFromInfoXml($this->extractPluginList($info)); |
|
| 129 | - foreach ($plugins as $plugin) { |
|
| 130 | - $this->plugins[] = $plugin; |
|
| 131 | - } |
|
| 132 | - |
|
| 133 | - $collections = $this->loadSabreCollectionsFromInfoXml($this->extractCollectionList($info)); |
|
| 134 | - foreach ($collections as $collection) { |
|
| 135 | - $this->collections[] = $collection; |
|
| 136 | - } |
|
| 137 | - |
|
| 138 | - $addresbookPlugins = $this->loadSabreAddressBookPluginsFromInfoXml($this->extractAddressBookPluginList($info)); |
|
| 139 | - foreach ($addresbookPlugins as $addresbookPlugin) { |
|
| 140 | - $this->addressBookPlugins[] = $addresbookPlugin; |
|
| 141 | - } |
|
| 142 | - |
|
| 143 | - $calendarPlugins = $this->loadSabreCalendarPluginsFromInfoXml($this->extractCalendarPluginList($info)); |
|
| 144 | - foreach ($calendarPlugins as $calendarPlugin) { |
|
| 145 | - $this->calendarPlugins[] = $calendarPlugin; |
|
| 146 | - } |
|
| 147 | - } |
|
| 148 | - } |
|
| 149 | - |
|
| 150 | - /** |
|
| 151 | - * @param array $array |
|
| 152 | - * @return string[] |
|
| 153 | - */ |
|
| 154 | - private function extractPluginList(array $array): array { |
|
| 155 | - if (isset($array['sabre']) && is_array($array['sabre'])) { |
|
| 156 | - if (isset($array['sabre']['plugins']) && is_array($array['sabre']['plugins'])) { |
|
| 157 | - if (isset($array['sabre']['plugins']['plugin'])) { |
|
| 158 | - $items = $array['sabre']['plugins']['plugin']; |
|
| 159 | - if (!is_array($items)) { |
|
| 160 | - $items = [$items]; |
|
| 161 | - } |
|
| 162 | - return $items; |
|
| 163 | - } |
|
| 164 | - } |
|
| 165 | - } |
|
| 166 | - return []; |
|
| 167 | - } |
|
| 168 | - |
|
| 169 | - /** |
|
| 170 | - * @param array $array |
|
| 171 | - * @return string[] |
|
| 172 | - */ |
|
| 173 | - private function extractCollectionList(array $array): array { |
|
| 174 | - if (isset($array['sabre']) && is_array($array['sabre'])) { |
|
| 175 | - if (isset($array['sabre']['collections']) && is_array($array['sabre']['collections'])) { |
|
| 176 | - if (isset($array['sabre']['collections']['collection'])) { |
|
| 177 | - $items = $array['sabre']['collections']['collection']; |
|
| 178 | - if (!is_array($items)) { |
|
| 179 | - $items = [$items]; |
|
| 180 | - } |
|
| 181 | - return $items; |
|
| 182 | - } |
|
| 183 | - } |
|
| 184 | - } |
|
| 185 | - return []; |
|
| 186 | - } |
|
| 187 | - |
|
| 188 | - /** |
|
| 189 | - * @param array $array |
|
| 190 | - * @return string[] |
|
| 191 | - */ |
|
| 192 | - private function extractAddressBookPluginList(array $array): array { |
|
| 193 | - if (!isset($array['sabre']) || !is_array($array['sabre'])) { |
|
| 194 | - return []; |
|
| 195 | - } |
|
| 196 | - if (!isset($array['sabre']['address-book-plugins']) || !is_array($array['sabre']['address-book-plugins'])) { |
|
| 197 | - return []; |
|
| 198 | - } |
|
| 199 | - if (!isset($array['sabre']['address-book-plugins']['plugin'])) { |
|
| 200 | - return []; |
|
| 201 | - } |
|
| 202 | - |
|
| 203 | - $items = $array['sabre']['address-book-plugins']['plugin']; |
|
| 204 | - if (!is_array($items)) { |
|
| 205 | - $items = [$items]; |
|
| 206 | - } |
|
| 207 | - return $items; |
|
| 208 | - } |
|
| 209 | - |
|
| 210 | - /** |
|
| 211 | - * @param array $array |
|
| 212 | - * @return string[] |
|
| 213 | - */ |
|
| 214 | - private function extractCalendarPluginList(array $array): array { |
|
| 215 | - if (isset($array['sabre']) && is_array($array['sabre'])) { |
|
| 216 | - if (isset($array['sabre']['calendar-plugins']) && is_array($array['sabre']['calendar-plugins'])) { |
|
| 217 | - if (isset($array['sabre']['calendar-plugins']['plugin'])) { |
|
| 218 | - $items = $array['sabre']['calendar-plugins']['plugin']; |
|
| 219 | - if (!is_array($items)) { |
|
| 220 | - $items = [$items]; |
|
| 221 | - } |
|
| 222 | - return $items; |
|
| 223 | - } |
|
| 224 | - } |
|
| 225 | - } |
|
| 226 | - return []; |
|
| 227 | - } |
|
| 228 | - |
|
| 229 | - private function createClass(string $className): object { |
|
| 230 | - try { |
|
| 231 | - return $this->container->get($className); |
|
| 232 | - } catch (ContainerExceptionInterface $e) { |
|
| 233 | - if (class_exists($className)) { |
|
| 234 | - return new $className(); |
|
| 235 | - } |
|
| 236 | - } |
|
| 237 | - |
|
| 238 | - throw new \Exception('Could not load ' . $className, 0, $e); |
|
| 239 | - } |
|
| 240 | - |
|
| 241 | - |
|
| 242 | - /** |
|
| 243 | - * @param string[] $classes |
|
| 244 | - * @return ServerPlugin[] |
|
| 245 | - * @throws \Exception |
|
| 246 | - */ |
|
| 247 | - private function loadSabrePluginsFromInfoXml(array $classes): array { |
|
| 248 | - return array_map(function (string $className): ServerPlugin { |
|
| 249 | - $instance = $this->createClass($className); |
|
| 250 | - if (!($instance instanceof ServerPlugin)) { |
|
| 251 | - throw new \Exception('Sabre server plugin ' . $className . ' does not implement the ' . ServerPlugin::class . ' interface'); |
|
| 252 | - } |
|
| 253 | - return $instance; |
|
| 254 | - }, $classes); |
|
| 255 | - } |
|
| 256 | - |
|
| 257 | - /** |
|
| 258 | - * @param string[] $classes |
|
| 259 | - * @return Collection[] |
|
| 260 | - */ |
|
| 261 | - private function loadSabreCollectionsFromInfoXml(array $classes): array { |
|
| 262 | - return array_map(function (string $className): Collection { |
|
| 263 | - $instance = $this->createClass($className); |
|
| 264 | - if (!($instance instanceof Collection)) { |
|
| 265 | - throw new \Exception('Sabre collection plugin ' . $className . ' does not implement the ' . Collection::class . ' interface'); |
|
| 266 | - } |
|
| 267 | - return $instance; |
|
| 268 | - }, $classes); |
|
| 269 | - } |
|
| 270 | - |
|
| 271 | - /** |
|
| 272 | - * @param string[] $classes |
|
| 273 | - * @return IAddressBookProvider[] |
|
| 274 | - */ |
|
| 275 | - private function loadSabreAddressBookPluginsFromInfoXml(array $classes): array { |
|
| 276 | - return array_map(function (string $className): IAddressBookProvider { |
|
| 277 | - $instance = $this->createClass($className); |
|
| 278 | - if (!($instance instanceof IAddressBookProvider)) { |
|
| 279 | - throw new \Exception('Sabre address book plugin class ' . $className . ' does not implement the ' . IAddressBookProvider::class . ' interface'); |
|
| 280 | - } |
|
| 281 | - return $instance; |
|
| 282 | - }, $classes); |
|
| 283 | - } |
|
| 284 | - |
|
| 285 | - /** |
|
| 286 | - * @param string[] $classes |
|
| 287 | - * @return ICalendarProvider[] |
|
| 288 | - */ |
|
| 289 | - private function loadSabreCalendarPluginsFromInfoXml(array $classes): array { |
|
| 290 | - return array_map(function (string $className): ICalendarProvider { |
|
| 291 | - $instance = $this->createClass($className); |
|
| 292 | - if (!($instance instanceof ICalendarProvider)) { |
|
| 293 | - throw new \Exception('Sabre calendar plugin class ' . $className . ' does not implement the ' . ICalendarProvider::class . ' interface'); |
|
| 294 | - } |
|
| 295 | - return $instance; |
|
| 296 | - }, $classes); |
|
| 297 | - } |
|
| 30 | + /** |
|
| 31 | + * App plugins |
|
| 32 | + * |
|
| 33 | + * @var ServerPlugin[] |
|
| 34 | + */ |
|
| 35 | + private $plugins = []; |
|
| 36 | + |
|
| 37 | + /** |
|
| 38 | + * App collections |
|
| 39 | + * |
|
| 40 | + * @var Collection[] |
|
| 41 | + */ |
|
| 42 | + private $collections = []; |
|
| 43 | + |
|
| 44 | + /** |
|
| 45 | + * Address book plugins |
|
| 46 | + * |
|
| 47 | + * @var IAddressBookProvider[] |
|
| 48 | + */ |
|
| 49 | + private $addressBookPlugins = []; |
|
| 50 | + |
|
| 51 | + /** |
|
| 52 | + * Calendar plugins |
|
| 53 | + * |
|
| 54 | + * @var ICalendarProvider[] |
|
| 55 | + */ |
|
| 56 | + private $calendarPlugins = []; |
|
| 57 | + |
|
| 58 | + /** @var bool */ |
|
| 59 | + private $populated = false; |
|
| 60 | + |
|
| 61 | + /** |
|
| 62 | + * Contstruct a PluginManager |
|
| 63 | + * |
|
| 64 | + * @param ServerContainer $container server container for resolving plugin classes |
|
| 65 | + * @param IAppManager $appManager app manager to loading apps and their info |
|
| 66 | + */ |
|
| 67 | + public function __construct( |
|
| 68 | + private ServerContainer $container, |
|
| 69 | + private IAppManager $appManager, |
|
| 70 | + ) { |
|
| 71 | + } |
|
| 72 | + |
|
| 73 | + /** |
|
| 74 | + * Returns an array of app-registered plugins |
|
| 75 | + * |
|
| 76 | + * @return ServerPlugin[] |
|
| 77 | + */ |
|
| 78 | + public function getAppPlugins() { |
|
| 79 | + $this->populate(); |
|
| 80 | + return $this->plugins; |
|
| 81 | + } |
|
| 82 | + |
|
| 83 | + /** |
|
| 84 | + * Returns an array of app-registered collections |
|
| 85 | + * |
|
| 86 | + * @return array |
|
| 87 | + */ |
|
| 88 | + public function getAppCollections() { |
|
| 89 | + $this->populate(); |
|
| 90 | + return $this->collections; |
|
| 91 | + } |
|
| 92 | + |
|
| 93 | + /** |
|
| 94 | + * @return IAddressBookProvider[] |
|
| 95 | + */ |
|
| 96 | + public function getAddressBookPlugins(): array { |
|
| 97 | + $this->populate(); |
|
| 98 | + return $this->addressBookPlugins; |
|
| 99 | + } |
|
| 100 | + |
|
| 101 | + /** |
|
| 102 | + * Returns an array of app-registered calendar plugins |
|
| 103 | + * |
|
| 104 | + * @return ICalendarProvider[] |
|
| 105 | + */ |
|
| 106 | + public function getCalendarPlugins():array { |
|
| 107 | + $this->populate(); |
|
| 108 | + return $this->calendarPlugins; |
|
| 109 | + } |
|
| 110 | + |
|
| 111 | + /** |
|
| 112 | + * Retrieve plugin and collection list and populate attributes |
|
| 113 | + */ |
|
| 114 | + private function populate(): void { |
|
| 115 | + if ($this->populated) { |
|
| 116 | + return; |
|
| 117 | + } |
|
| 118 | + $this->populated = true; |
|
| 119 | + |
|
| 120 | + $this->calendarPlugins[] = $this->container->get(AppCalendarPlugin::class); |
|
| 121 | + |
|
| 122 | + foreach ($this->appManager->getEnabledApps() as $app) { |
|
| 123 | + // load plugins and collections from info.xml |
|
| 124 | + $info = $this->appManager->getAppInfo($app); |
|
| 125 | + if (!isset($info['types']) || !in_array('dav', $info['types'], true)) { |
|
| 126 | + continue; |
|
| 127 | + } |
|
| 128 | + $plugins = $this->loadSabrePluginsFromInfoXml($this->extractPluginList($info)); |
|
| 129 | + foreach ($plugins as $plugin) { |
|
| 130 | + $this->plugins[] = $plugin; |
|
| 131 | + } |
|
| 132 | + |
|
| 133 | + $collections = $this->loadSabreCollectionsFromInfoXml($this->extractCollectionList($info)); |
|
| 134 | + foreach ($collections as $collection) { |
|
| 135 | + $this->collections[] = $collection; |
|
| 136 | + } |
|
| 137 | + |
|
| 138 | + $addresbookPlugins = $this->loadSabreAddressBookPluginsFromInfoXml($this->extractAddressBookPluginList($info)); |
|
| 139 | + foreach ($addresbookPlugins as $addresbookPlugin) { |
|
| 140 | + $this->addressBookPlugins[] = $addresbookPlugin; |
|
| 141 | + } |
|
| 142 | + |
|
| 143 | + $calendarPlugins = $this->loadSabreCalendarPluginsFromInfoXml($this->extractCalendarPluginList($info)); |
|
| 144 | + foreach ($calendarPlugins as $calendarPlugin) { |
|
| 145 | + $this->calendarPlugins[] = $calendarPlugin; |
|
| 146 | + } |
|
| 147 | + } |
|
| 148 | + } |
|
| 149 | + |
|
| 150 | + /** |
|
| 151 | + * @param array $array |
|
| 152 | + * @return string[] |
|
| 153 | + */ |
|
| 154 | + private function extractPluginList(array $array): array { |
|
| 155 | + if (isset($array['sabre']) && is_array($array['sabre'])) { |
|
| 156 | + if (isset($array['sabre']['plugins']) && is_array($array['sabre']['plugins'])) { |
|
| 157 | + if (isset($array['sabre']['plugins']['plugin'])) { |
|
| 158 | + $items = $array['sabre']['plugins']['plugin']; |
|
| 159 | + if (!is_array($items)) { |
|
| 160 | + $items = [$items]; |
|
| 161 | + } |
|
| 162 | + return $items; |
|
| 163 | + } |
|
| 164 | + } |
|
| 165 | + } |
|
| 166 | + return []; |
|
| 167 | + } |
|
| 168 | + |
|
| 169 | + /** |
|
| 170 | + * @param array $array |
|
| 171 | + * @return string[] |
|
| 172 | + */ |
|
| 173 | + private function extractCollectionList(array $array): array { |
|
| 174 | + if (isset($array['sabre']) && is_array($array['sabre'])) { |
|
| 175 | + if (isset($array['sabre']['collections']) && is_array($array['sabre']['collections'])) { |
|
| 176 | + if (isset($array['sabre']['collections']['collection'])) { |
|
| 177 | + $items = $array['sabre']['collections']['collection']; |
|
| 178 | + if (!is_array($items)) { |
|
| 179 | + $items = [$items]; |
|
| 180 | + } |
|
| 181 | + return $items; |
|
| 182 | + } |
|
| 183 | + } |
|
| 184 | + } |
|
| 185 | + return []; |
|
| 186 | + } |
|
| 187 | + |
|
| 188 | + /** |
|
| 189 | + * @param array $array |
|
| 190 | + * @return string[] |
|
| 191 | + */ |
|
| 192 | + private function extractAddressBookPluginList(array $array): array { |
|
| 193 | + if (!isset($array['sabre']) || !is_array($array['sabre'])) { |
|
| 194 | + return []; |
|
| 195 | + } |
|
| 196 | + if (!isset($array['sabre']['address-book-plugins']) || !is_array($array['sabre']['address-book-plugins'])) { |
|
| 197 | + return []; |
|
| 198 | + } |
|
| 199 | + if (!isset($array['sabre']['address-book-plugins']['plugin'])) { |
|
| 200 | + return []; |
|
| 201 | + } |
|
| 202 | + |
|
| 203 | + $items = $array['sabre']['address-book-plugins']['plugin']; |
|
| 204 | + if (!is_array($items)) { |
|
| 205 | + $items = [$items]; |
|
| 206 | + } |
|
| 207 | + return $items; |
|
| 208 | + } |
|
| 209 | + |
|
| 210 | + /** |
|
| 211 | + * @param array $array |
|
| 212 | + * @return string[] |
|
| 213 | + */ |
|
| 214 | + private function extractCalendarPluginList(array $array): array { |
|
| 215 | + if (isset($array['sabre']) && is_array($array['sabre'])) { |
|
| 216 | + if (isset($array['sabre']['calendar-plugins']) && is_array($array['sabre']['calendar-plugins'])) { |
|
| 217 | + if (isset($array['sabre']['calendar-plugins']['plugin'])) { |
|
| 218 | + $items = $array['sabre']['calendar-plugins']['plugin']; |
|
| 219 | + if (!is_array($items)) { |
|
| 220 | + $items = [$items]; |
|
| 221 | + } |
|
| 222 | + return $items; |
|
| 223 | + } |
|
| 224 | + } |
|
| 225 | + } |
|
| 226 | + return []; |
|
| 227 | + } |
|
| 228 | + |
|
| 229 | + private function createClass(string $className): object { |
|
| 230 | + try { |
|
| 231 | + return $this->container->get($className); |
|
| 232 | + } catch (ContainerExceptionInterface $e) { |
|
| 233 | + if (class_exists($className)) { |
|
| 234 | + return new $className(); |
|
| 235 | + } |
|
| 236 | + } |
|
| 237 | + |
|
| 238 | + throw new \Exception('Could not load ' . $className, 0, $e); |
|
| 239 | + } |
|
| 240 | + |
|
| 241 | + |
|
| 242 | + /** |
|
| 243 | + * @param string[] $classes |
|
| 244 | + * @return ServerPlugin[] |
|
| 245 | + * @throws \Exception |
|
| 246 | + */ |
|
| 247 | + private function loadSabrePluginsFromInfoXml(array $classes): array { |
|
| 248 | + return array_map(function (string $className): ServerPlugin { |
|
| 249 | + $instance = $this->createClass($className); |
|
| 250 | + if (!($instance instanceof ServerPlugin)) { |
|
| 251 | + throw new \Exception('Sabre server plugin ' . $className . ' does not implement the ' . ServerPlugin::class . ' interface'); |
|
| 252 | + } |
|
| 253 | + return $instance; |
|
| 254 | + }, $classes); |
|
| 255 | + } |
|
| 256 | + |
|
| 257 | + /** |
|
| 258 | + * @param string[] $classes |
|
| 259 | + * @return Collection[] |
|
| 260 | + */ |
|
| 261 | + private function loadSabreCollectionsFromInfoXml(array $classes): array { |
|
| 262 | + return array_map(function (string $className): Collection { |
|
| 263 | + $instance = $this->createClass($className); |
|
| 264 | + if (!($instance instanceof Collection)) { |
|
| 265 | + throw new \Exception('Sabre collection plugin ' . $className . ' does not implement the ' . Collection::class . ' interface'); |
|
| 266 | + } |
|
| 267 | + return $instance; |
|
| 268 | + }, $classes); |
|
| 269 | + } |
|
| 270 | + |
|
| 271 | + /** |
|
| 272 | + * @param string[] $classes |
|
| 273 | + * @return IAddressBookProvider[] |
|
| 274 | + */ |
|
| 275 | + private function loadSabreAddressBookPluginsFromInfoXml(array $classes): array { |
|
| 276 | + return array_map(function (string $className): IAddressBookProvider { |
|
| 277 | + $instance = $this->createClass($className); |
|
| 278 | + if (!($instance instanceof IAddressBookProvider)) { |
|
| 279 | + throw new \Exception('Sabre address book plugin class ' . $className . ' does not implement the ' . IAddressBookProvider::class . ' interface'); |
|
| 280 | + } |
|
| 281 | + return $instance; |
|
| 282 | + }, $classes); |
|
| 283 | + } |
|
| 284 | + |
|
| 285 | + /** |
|
| 286 | + * @param string[] $classes |
|
| 287 | + * @return ICalendarProvider[] |
|
| 288 | + */ |
|
| 289 | + private function loadSabreCalendarPluginsFromInfoXml(array $classes): array { |
|
| 290 | + return array_map(function (string $className): ICalendarProvider { |
|
| 291 | + $instance = $this->createClass($className); |
|
| 292 | + if (!($instance instanceof ICalendarProvider)) { |
|
| 293 | + throw new \Exception('Sabre calendar plugin class ' . $className . ' does not implement the ' . ICalendarProvider::class . ' interface'); |
|
| 294 | + } |
|
| 295 | + return $instance; |
|
| 296 | + }, $classes); |
|
| 297 | + } |
|
| 298 | 298 | } |
@@ -27,176 +27,176 @@ |
||
| 27 | 27 | * Class to configure mount.json globally and for users |
| 28 | 28 | */ |
| 29 | 29 | class MountConfig { |
| 30 | - // TODO: make this class non-static and give it a proper namespace |
|
| 30 | + // TODO: make this class non-static and give it a proper namespace |
|
| 31 | 31 | |
| 32 | - public const MOUNT_TYPE_GLOBAL = 'global'; |
|
| 33 | - public const MOUNT_TYPE_GROUP = 'group'; |
|
| 34 | - public const MOUNT_TYPE_USER = 'user'; |
|
| 35 | - public const MOUNT_TYPE_PERSONAL = 'personal'; |
|
| 32 | + public const MOUNT_TYPE_GLOBAL = 'global'; |
|
| 33 | + public const MOUNT_TYPE_GROUP = 'group'; |
|
| 34 | + public const MOUNT_TYPE_USER = 'user'; |
|
| 35 | + public const MOUNT_TYPE_PERSONAL = 'personal'; |
|
| 36 | 36 | |
| 37 | - // whether to skip backend test (for unit tests, as this static class is not mockable) |
|
| 38 | - public static $skipTest = false; |
|
| 37 | + // whether to skip backend test (for unit tests, as this static class is not mockable) |
|
| 38 | + public static $skipTest = false; |
|
| 39 | 39 | |
| 40 | - public function __construct( |
|
| 41 | - private UserGlobalStoragesService $userGlobalStorageService, |
|
| 42 | - private UserStoragesService $userStorageService, |
|
| 43 | - private GlobalStoragesService $globalStorageService, |
|
| 44 | - ) { |
|
| 45 | - } |
|
| 40 | + public function __construct( |
|
| 41 | + private UserGlobalStoragesService $userGlobalStorageService, |
|
| 42 | + private UserStoragesService $userStorageService, |
|
| 43 | + private GlobalStoragesService $globalStorageService, |
|
| 44 | + ) { |
|
| 45 | + } |
|
| 46 | 46 | |
| 47 | - /** |
|
| 48 | - * @param mixed $input |
|
| 49 | - * @param string|null $userId |
|
| 50 | - * @return mixed |
|
| 51 | - * @throws ContainerExceptionInterface |
|
| 52 | - * @since 16.0.0 |
|
| 53 | - */ |
|
| 54 | - public static function substitutePlaceholdersInConfig($input, ?string $userId = null) { |
|
| 55 | - /** @var BackendService $backendService */ |
|
| 56 | - $backendService = Server::get(BackendService::class); |
|
| 57 | - /** @var IConfigHandler[] $handlers */ |
|
| 58 | - $handlers = $backendService->getConfigHandlers(); |
|
| 59 | - foreach ($handlers as $handler) { |
|
| 60 | - if ($handler instanceof UserContext && $userId !== null) { |
|
| 61 | - $handler->setUserId($userId); |
|
| 62 | - } |
|
| 63 | - $input = $handler->handle($input); |
|
| 64 | - } |
|
| 65 | - return $input; |
|
| 66 | - } |
|
| 47 | + /** |
|
| 48 | + * @param mixed $input |
|
| 49 | + * @param string|null $userId |
|
| 50 | + * @return mixed |
|
| 51 | + * @throws ContainerExceptionInterface |
|
| 52 | + * @since 16.0.0 |
|
| 53 | + */ |
|
| 54 | + public static function substitutePlaceholdersInConfig($input, ?string $userId = null) { |
|
| 55 | + /** @var BackendService $backendService */ |
|
| 56 | + $backendService = Server::get(BackendService::class); |
|
| 57 | + /** @var IConfigHandler[] $handlers */ |
|
| 58 | + $handlers = $backendService->getConfigHandlers(); |
|
| 59 | + foreach ($handlers as $handler) { |
|
| 60 | + if ($handler instanceof UserContext && $userId !== null) { |
|
| 61 | + $handler->setUserId($userId); |
|
| 62 | + } |
|
| 63 | + $input = $handler->handle($input); |
|
| 64 | + } |
|
| 65 | + return $input; |
|
| 66 | + } |
|
| 67 | 67 | |
| 68 | - /** |
|
| 69 | - * Test connecting using the given backend configuration |
|
| 70 | - * |
|
| 71 | - * @param string $class backend class name |
|
| 72 | - * @param array $options backend configuration options |
|
| 73 | - * @param boolean $isPersonal |
|
| 74 | - * @return int see self::STATUS_* |
|
| 75 | - * @throws \Exception |
|
| 76 | - */ |
|
| 77 | - public static function getBackendStatus($class, $options) { |
|
| 78 | - if (self::$skipTest) { |
|
| 79 | - return StorageNotAvailableException::STATUS_SUCCESS; |
|
| 80 | - } |
|
| 81 | - foreach ($options as $key => &$option) { |
|
| 82 | - if ($key === 'password') { |
|
| 83 | - // no replacements in passwords |
|
| 84 | - continue; |
|
| 85 | - } |
|
| 86 | - $option = self::substitutePlaceholdersInConfig($option); |
|
| 87 | - } |
|
| 88 | - if (class_exists($class)) { |
|
| 89 | - try { |
|
| 90 | - /** @var Common $storage */ |
|
| 91 | - $storage = new $class($options); |
|
| 68 | + /** |
|
| 69 | + * Test connecting using the given backend configuration |
|
| 70 | + * |
|
| 71 | + * @param string $class backend class name |
|
| 72 | + * @param array $options backend configuration options |
|
| 73 | + * @param boolean $isPersonal |
|
| 74 | + * @return int see self::STATUS_* |
|
| 75 | + * @throws \Exception |
|
| 76 | + */ |
|
| 77 | + public static function getBackendStatus($class, $options) { |
|
| 78 | + if (self::$skipTest) { |
|
| 79 | + return StorageNotAvailableException::STATUS_SUCCESS; |
|
| 80 | + } |
|
| 81 | + foreach ($options as $key => &$option) { |
|
| 82 | + if ($key === 'password') { |
|
| 83 | + // no replacements in passwords |
|
| 84 | + continue; |
|
| 85 | + } |
|
| 86 | + $option = self::substitutePlaceholdersInConfig($option); |
|
| 87 | + } |
|
| 88 | + if (class_exists($class)) { |
|
| 89 | + try { |
|
| 90 | + /** @var Common $storage */ |
|
| 91 | + $storage = new $class($options); |
|
| 92 | 92 | |
| 93 | - try { |
|
| 94 | - $result = $storage->test(); |
|
| 95 | - $storage->setAvailability($result); |
|
| 96 | - if ($result) { |
|
| 97 | - return StorageNotAvailableException::STATUS_SUCCESS; |
|
| 98 | - } |
|
| 99 | - } catch (\Exception $e) { |
|
| 100 | - $storage->setAvailability(false); |
|
| 101 | - throw $e; |
|
| 102 | - } |
|
| 103 | - } catch (\Exception $exception) { |
|
| 104 | - Server::get(LoggerInterface::class)->error($exception->getMessage(), ['exception' => $exception, 'app' => 'files_external']); |
|
| 105 | - throw $exception; |
|
| 106 | - } |
|
| 107 | - } |
|
| 108 | - return StorageNotAvailableException::STATUS_ERROR; |
|
| 109 | - } |
|
| 93 | + try { |
|
| 94 | + $result = $storage->test(); |
|
| 95 | + $storage->setAvailability($result); |
|
| 96 | + if ($result) { |
|
| 97 | + return StorageNotAvailableException::STATUS_SUCCESS; |
|
| 98 | + } |
|
| 99 | + } catch (\Exception $e) { |
|
| 100 | + $storage->setAvailability(false); |
|
| 101 | + throw $e; |
|
| 102 | + } |
|
| 103 | + } catch (\Exception $exception) { |
|
| 104 | + Server::get(LoggerInterface::class)->error($exception->getMessage(), ['exception' => $exception, 'app' => 'files_external']); |
|
| 105 | + throw $exception; |
|
| 106 | + } |
|
| 107 | + } |
|
| 108 | + return StorageNotAvailableException::STATUS_ERROR; |
|
| 109 | + } |
|
| 110 | 110 | |
| 111 | - /** |
|
| 112 | - * Encrypt passwords in the given config options |
|
| 113 | - * |
|
| 114 | - * @param array $options mount options |
|
| 115 | - * @return array updated options |
|
| 116 | - */ |
|
| 117 | - public static function encryptPasswords($options) { |
|
| 118 | - if (isset($options['password'])) { |
|
| 119 | - $options['password_encrypted'] = self::encryptPassword($options['password']); |
|
| 120 | - // do not unset the password, we want to keep the keys order |
|
| 121 | - // on load... because that's how the UI currently works |
|
| 122 | - $options['password'] = ''; |
|
| 123 | - } |
|
| 124 | - return $options; |
|
| 125 | - } |
|
| 111 | + /** |
|
| 112 | + * Encrypt passwords in the given config options |
|
| 113 | + * |
|
| 114 | + * @param array $options mount options |
|
| 115 | + * @return array updated options |
|
| 116 | + */ |
|
| 117 | + public static function encryptPasswords($options) { |
|
| 118 | + if (isset($options['password'])) { |
|
| 119 | + $options['password_encrypted'] = self::encryptPassword($options['password']); |
|
| 120 | + // do not unset the password, we want to keep the keys order |
|
| 121 | + // on load... because that's how the UI currently works |
|
| 122 | + $options['password'] = ''; |
|
| 123 | + } |
|
| 124 | + return $options; |
|
| 125 | + } |
|
| 126 | 126 | |
| 127 | - /** |
|
| 128 | - * Decrypt passwords in the given config options |
|
| 129 | - * |
|
| 130 | - * @param array $options mount options |
|
| 131 | - * @return array updated options |
|
| 132 | - */ |
|
| 133 | - public static function decryptPasswords($options) { |
|
| 134 | - // note: legacy options might still have the unencrypted password in the "password" field |
|
| 135 | - if (isset($options['password_encrypted'])) { |
|
| 136 | - $options['password'] = self::decryptPassword($options['password_encrypted']); |
|
| 137 | - unset($options['password_encrypted']); |
|
| 138 | - } |
|
| 139 | - return $options; |
|
| 140 | - } |
|
| 127 | + /** |
|
| 128 | + * Decrypt passwords in the given config options |
|
| 129 | + * |
|
| 130 | + * @param array $options mount options |
|
| 131 | + * @return array updated options |
|
| 132 | + */ |
|
| 133 | + public static function decryptPasswords($options) { |
|
| 134 | + // note: legacy options might still have the unencrypted password in the "password" field |
|
| 135 | + if (isset($options['password_encrypted'])) { |
|
| 136 | + $options['password'] = self::decryptPassword($options['password_encrypted']); |
|
| 137 | + unset($options['password_encrypted']); |
|
| 138 | + } |
|
| 139 | + return $options; |
|
| 140 | + } |
|
| 141 | 141 | |
| 142 | - /** |
|
| 143 | - * Encrypt a single password |
|
| 144 | - * |
|
| 145 | - * @param string $password plain text password |
|
| 146 | - * @return string encrypted password |
|
| 147 | - */ |
|
| 148 | - private static function encryptPassword($password) { |
|
| 149 | - $cipher = self::getCipher(); |
|
| 150 | - $iv = Server::get(ISecureRandom::class)->generate(16); |
|
| 151 | - $cipher->setIV($iv); |
|
| 152 | - return base64_encode($iv . $cipher->encrypt($password)); |
|
| 153 | - } |
|
| 142 | + /** |
|
| 143 | + * Encrypt a single password |
|
| 144 | + * |
|
| 145 | + * @param string $password plain text password |
|
| 146 | + * @return string encrypted password |
|
| 147 | + */ |
|
| 148 | + private static function encryptPassword($password) { |
|
| 149 | + $cipher = self::getCipher(); |
|
| 150 | + $iv = Server::get(ISecureRandom::class)->generate(16); |
|
| 151 | + $cipher->setIV($iv); |
|
| 152 | + return base64_encode($iv . $cipher->encrypt($password)); |
|
| 153 | + } |
|
| 154 | 154 | |
| 155 | - /** |
|
| 156 | - * Decrypts a single password |
|
| 157 | - * |
|
| 158 | - * @param string $encryptedPassword encrypted password |
|
| 159 | - * @return string plain text password |
|
| 160 | - */ |
|
| 161 | - private static function decryptPassword($encryptedPassword) { |
|
| 162 | - $cipher = self::getCipher(); |
|
| 163 | - $binaryPassword = base64_decode($encryptedPassword); |
|
| 164 | - $iv = substr($binaryPassword, 0, 16); |
|
| 165 | - $cipher->setIV($iv); |
|
| 166 | - $binaryPassword = substr($binaryPassword, 16); |
|
| 167 | - return $cipher->decrypt($binaryPassword); |
|
| 168 | - } |
|
| 155 | + /** |
|
| 156 | + * Decrypts a single password |
|
| 157 | + * |
|
| 158 | + * @param string $encryptedPassword encrypted password |
|
| 159 | + * @return string plain text password |
|
| 160 | + */ |
|
| 161 | + private static function decryptPassword($encryptedPassword) { |
|
| 162 | + $cipher = self::getCipher(); |
|
| 163 | + $binaryPassword = base64_decode($encryptedPassword); |
|
| 164 | + $iv = substr($binaryPassword, 0, 16); |
|
| 165 | + $cipher->setIV($iv); |
|
| 166 | + $binaryPassword = substr($binaryPassword, 16); |
|
| 167 | + return $cipher->decrypt($binaryPassword); |
|
| 168 | + } |
|
| 169 | 169 | |
| 170 | - /** |
|
| 171 | - * Returns the encryption cipher |
|
| 172 | - * |
|
| 173 | - * @return AES |
|
| 174 | - */ |
|
| 175 | - private static function getCipher() { |
|
| 176 | - $cipher = new AES(AES::MODE_CBC); |
|
| 177 | - $cipher->setKey(Server::get(IConfig::class)->getSystemValue('passwordsalt', null)); |
|
| 178 | - return $cipher; |
|
| 179 | - } |
|
| 170 | + /** |
|
| 171 | + * Returns the encryption cipher |
|
| 172 | + * |
|
| 173 | + * @return AES |
|
| 174 | + */ |
|
| 175 | + private static function getCipher() { |
|
| 176 | + $cipher = new AES(AES::MODE_CBC); |
|
| 177 | + $cipher->setKey(Server::get(IConfig::class)->getSystemValue('passwordsalt', null)); |
|
| 178 | + return $cipher; |
|
| 179 | + } |
|
| 180 | 180 | |
| 181 | - /** |
|
| 182 | - * Computes a hash based on the given configuration. |
|
| 183 | - * This is mostly used to find out whether configurations |
|
| 184 | - * are the same. |
|
| 185 | - * |
|
| 186 | - * @param array $config |
|
| 187 | - * @return string |
|
| 188 | - */ |
|
| 189 | - public static function makeConfigHash($config) { |
|
| 190 | - $data = json_encode( |
|
| 191 | - [ |
|
| 192 | - 'c' => $config['backend'], |
|
| 193 | - 'a' => $config['authMechanism'], |
|
| 194 | - 'm' => $config['mountpoint'], |
|
| 195 | - 'o' => $config['options'], |
|
| 196 | - 'p' => $config['priority'] ?? -1, |
|
| 197 | - 'mo' => $config['mountOptions'] ?? [], |
|
| 198 | - ] |
|
| 199 | - ); |
|
| 200 | - return hash('md5', $data); |
|
| 201 | - } |
|
| 181 | + /** |
|
| 182 | + * Computes a hash based on the given configuration. |
|
| 183 | + * This is mostly used to find out whether configurations |
|
| 184 | + * are the same. |
|
| 185 | + * |
|
| 186 | + * @param array $config |
|
| 187 | + * @return string |
|
| 188 | + */ |
|
| 189 | + public static function makeConfigHash($config) { |
|
| 190 | + $data = json_encode( |
|
| 191 | + [ |
|
| 192 | + 'c' => $config['backend'], |
|
| 193 | + 'a' => $config['authMechanism'], |
|
| 194 | + 'm' => $config['mountpoint'], |
|
| 195 | + 'o' => $config['options'], |
|
| 196 | + 'p' => $config['priority'] ?? -1, |
|
| 197 | + 'mo' => $config['mountOptions'] ?? [], |
|
| 198 | + ] |
|
| 199 | + ); |
|
| 200 | + return hash('md5', $data); |
|
| 201 | + } |
|
| 202 | 202 | } |
@@ -34,143 +34,143 @@ |
||
| 34 | 34 | * Make the old files_external config work with the new public mount config api |
| 35 | 35 | */ |
| 36 | 36 | class ConfigAdapter implements IMountProvider, IAuthoritativeMountProvider { |
| 37 | - public function __construct( |
|
| 38 | - private UserStoragesService $userStoragesService, |
|
| 39 | - private UserGlobalStoragesService $userGlobalStoragesService, |
|
| 40 | - private ClockInterface $clock, |
|
| 41 | - ) { |
|
| 42 | - } |
|
| 43 | - |
|
| 44 | - /** |
|
| 45 | - * @param class-string $class |
|
| 46 | - * @return class-string<IObjectStore> |
|
| 47 | - * @throws \InvalidArgumentException |
|
| 48 | - * @psalm-taint-escape callable |
|
| 49 | - */ |
|
| 50 | - private function validateObjectStoreClassString(string $class): string { |
|
| 51 | - if (!\is_subclass_of($class, IObjectStore::class)) { |
|
| 52 | - throw new \InvalidArgumentException('Invalid object store'); |
|
| 53 | - } |
|
| 54 | - return $class; |
|
| 55 | - } |
|
| 56 | - |
|
| 57 | - /** |
|
| 58 | - * Process storage ready for mounting |
|
| 59 | - * |
|
| 60 | - * @throws ContainerExceptionInterface |
|
| 61 | - */ |
|
| 62 | - private function prepareStorageConfig(StorageConfig &$storage, IUser $user): void { |
|
| 63 | - foreach ($storage->getBackendOptions() as $option => $value) { |
|
| 64 | - $storage->setBackendOption($option, MountConfig::substitutePlaceholdersInConfig($value, $user->getUID())); |
|
| 65 | - } |
|
| 66 | - |
|
| 67 | - $objectStore = $storage->getBackendOption('objectstore'); |
|
| 68 | - if ($objectStore) { |
|
| 69 | - $objectClass = $this->validateObjectStoreClassString($objectStore['class']); |
|
| 70 | - $storage->setBackendOption('objectstore', new $objectClass($objectStore)); |
|
| 71 | - } |
|
| 72 | - |
|
| 73 | - $storage->getAuthMechanism()->manipulateStorageConfig($storage, $user); |
|
| 74 | - $storage->getBackend()->manipulateStorageConfig($storage, $user); |
|
| 75 | - } |
|
| 76 | - |
|
| 77 | - public function constructStorageForUser(IUser $user, StorageConfig $storage) { |
|
| 78 | - $this->prepareStorageConfig($storage, $user); |
|
| 79 | - return $this->constructStorage($storage); |
|
| 80 | - } |
|
| 81 | - |
|
| 82 | - /** |
|
| 83 | - * Construct the storage implementation |
|
| 84 | - * |
|
| 85 | - * @param StorageConfig $storageConfig |
|
| 86 | - */ |
|
| 87 | - private function constructStorage(StorageConfig $storageConfig): IStorage { |
|
| 88 | - $class = $storageConfig->getBackend()->getStorageClass(); |
|
| 89 | - if (!is_a($class, IConstructableStorage::class, true)) { |
|
| 90 | - Server::get(LoggerInterface::class)->warning('Building a storage not implementing IConstructableStorage is deprecated since 31.0.0', ['class' => $class]); |
|
| 91 | - } |
|
| 92 | - $storage = new $class($storageConfig->getBackendOptions()); |
|
| 93 | - |
|
| 94 | - // auth mechanism should fire first |
|
| 95 | - $storage = $storageConfig->getBackend()->wrapStorage($storage); |
|
| 96 | - $storage = $storageConfig->getAuthMechanism()->wrapStorage($storage); |
|
| 97 | - |
|
| 98 | - return $storage; |
|
| 99 | - } |
|
| 100 | - |
|
| 101 | - /** |
|
| 102 | - * Get all mountpoints applicable for the user |
|
| 103 | - * |
|
| 104 | - * @return IMountPoint[] |
|
| 105 | - */ |
|
| 106 | - public function getMountsForUser(IUser $user, IStorageFactory $loader) { |
|
| 107 | - $this->userStoragesService->setUser($user); |
|
| 108 | - $this->userGlobalStoragesService->setUser($user); |
|
| 109 | - |
|
| 110 | - $storageConfigs = $this->userGlobalStoragesService->getAllStoragesForUser(); |
|
| 111 | - |
|
| 112 | - $storages = array_map(function (StorageConfig $storageConfig) use ($user) { |
|
| 113 | - try { |
|
| 114 | - return $this->constructStorageForUser($user, $storageConfig); |
|
| 115 | - } catch (\Exception $e) { |
|
| 116 | - // propagate exception into filesystem |
|
| 117 | - return new FailedStorage(['exception' => $e]); |
|
| 118 | - } |
|
| 119 | - }, $storageConfigs); |
|
| 120 | - |
|
| 121 | - |
|
| 122 | - Storage::getGlobalCache()->loadForStorageIds(array_map(function (IStorage $storage) { |
|
| 123 | - return $storage->getId(); |
|
| 124 | - }, $storages)); |
|
| 125 | - |
|
| 126 | - $availableStorages = array_map(function (IStorage $storage, StorageConfig $storageConfig): IStorage { |
|
| 127 | - try { |
|
| 128 | - $availability = $storage->getAvailability(); |
|
| 129 | - if (!$availability['available'] && !Availability::shouldRecheck($availability)) { |
|
| 130 | - $storage = new FailedStorage([ |
|
| 131 | - 'exception' => new StorageNotAvailableException('Storage with mount id ' . $storageConfig->getId() . ' is not available'), |
|
| 132 | - ]); |
|
| 133 | - } |
|
| 134 | - } catch (\Exception $e) { |
|
| 135 | - // propagate exception into filesystem |
|
| 136 | - $storage = new FailedStorage(['exception' => $e]); |
|
| 137 | - } |
|
| 138 | - return $storage; |
|
| 139 | - }, $storages, $storageConfigs); |
|
| 140 | - |
|
| 141 | - $mounts = array_map(function (StorageConfig $storageConfig, IStorage $storage) use ($user, $loader) { |
|
| 142 | - $storage->setOwner($user->getUID()); |
|
| 143 | - if ($storageConfig->getType() === StorageConfig::MOUNT_TYPE_PERSONAL) { |
|
| 144 | - return new PersonalMount( |
|
| 145 | - $this->userStoragesService, |
|
| 146 | - $storageConfig, |
|
| 147 | - $storageConfig->getId(), |
|
| 148 | - new KnownMtime([ |
|
| 149 | - 'storage' => $storage, |
|
| 150 | - 'clock' => $this->clock, |
|
| 151 | - ]), |
|
| 152 | - '/' . $user->getUID() . '/files' . $storageConfig->getMountPoint(), |
|
| 153 | - null, |
|
| 154 | - $loader, |
|
| 155 | - $storageConfig->getMountOptions(), |
|
| 156 | - $storageConfig->getId(), |
|
| 157 | - ); |
|
| 158 | - } else { |
|
| 159 | - return new SystemMountPoint( |
|
| 160 | - $storageConfig, |
|
| 161 | - $storage, |
|
| 162 | - '/' . $user->getUID() . '/files' . $storageConfig->getMountPoint(), |
|
| 163 | - null, |
|
| 164 | - $loader, |
|
| 165 | - $storageConfig->getMountOptions(), |
|
| 166 | - $storageConfig->getId(), |
|
| 167 | - ); |
|
| 168 | - } |
|
| 169 | - }, $storageConfigs, $availableStorages); |
|
| 170 | - |
|
| 171 | - $this->userStoragesService->resetUser(); |
|
| 172 | - $this->userGlobalStoragesService->resetUser(); |
|
| 173 | - |
|
| 174 | - return $mounts; |
|
| 175 | - } |
|
| 37 | + public function __construct( |
|
| 38 | + private UserStoragesService $userStoragesService, |
|
| 39 | + private UserGlobalStoragesService $userGlobalStoragesService, |
|
| 40 | + private ClockInterface $clock, |
|
| 41 | + ) { |
|
| 42 | + } |
|
| 43 | + |
|
| 44 | + /** |
|
| 45 | + * @param class-string $class |
|
| 46 | + * @return class-string<IObjectStore> |
|
| 47 | + * @throws \InvalidArgumentException |
|
| 48 | + * @psalm-taint-escape callable |
|
| 49 | + */ |
|
| 50 | + private function validateObjectStoreClassString(string $class): string { |
|
| 51 | + if (!\is_subclass_of($class, IObjectStore::class)) { |
|
| 52 | + throw new \InvalidArgumentException('Invalid object store'); |
|
| 53 | + } |
|
| 54 | + return $class; |
|
| 55 | + } |
|
| 56 | + |
|
| 57 | + /** |
|
| 58 | + * Process storage ready for mounting |
|
| 59 | + * |
|
| 60 | + * @throws ContainerExceptionInterface |
|
| 61 | + */ |
|
| 62 | + private function prepareStorageConfig(StorageConfig &$storage, IUser $user): void { |
|
| 63 | + foreach ($storage->getBackendOptions() as $option => $value) { |
|
| 64 | + $storage->setBackendOption($option, MountConfig::substitutePlaceholdersInConfig($value, $user->getUID())); |
|
| 65 | + } |
|
| 66 | + |
|
| 67 | + $objectStore = $storage->getBackendOption('objectstore'); |
|
| 68 | + if ($objectStore) { |
|
| 69 | + $objectClass = $this->validateObjectStoreClassString($objectStore['class']); |
|
| 70 | + $storage->setBackendOption('objectstore', new $objectClass($objectStore)); |
|
| 71 | + } |
|
| 72 | + |
|
| 73 | + $storage->getAuthMechanism()->manipulateStorageConfig($storage, $user); |
|
| 74 | + $storage->getBackend()->manipulateStorageConfig($storage, $user); |
|
| 75 | + } |
|
| 76 | + |
|
| 77 | + public function constructStorageForUser(IUser $user, StorageConfig $storage) { |
|
| 78 | + $this->prepareStorageConfig($storage, $user); |
|
| 79 | + return $this->constructStorage($storage); |
|
| 80 | + } |
|
| 81 | + |
|
| 82 | + /** |
|
| 83 | + * Construct the storage implementation |
|
| 84 | + * |
|
| 85 | + * @param StorageConfig $storageConfig |
|
| 86 | + */ |
|
| 87 | + private function constructStorage(StorageConfig $storageConfig): IStorage { |
|
| 88 | + $class = $storageConfig->getBackend()->getStorageClass(); |
|
| 89 | + if (!is_a($class, IConstructableStorage::class, true)) { |
|
| 90 | + Server::get(LoggerInterface::class)->warning('Building a storage not implementing IConstructableStorage is deprecated since 31.0.0', ['class' => $class]); |
|
| 91 | + } |
|
| 92 | + $storage = new $class($storageConfig->getBackendOptions()); |
|
| 93 | + |
|
| 94 | + // auth mechanism should fire first |
|
| 95 | + $storage = $storageConfig->getBackend()->wrapStorage($storage); |
|
| 96 | + $storage = $storageConfig->getAuthMechanism()->wrapStorage($storage); |
|
| 97 | + |
|
| 98 | + return $storage; |
|
| 99 | + } |
|
| 100 | + |
|
| 101 | + /** |
|
| 102 | + * Get all mountpoints applicable for the user |
|
| 103 | + * |
|
| 104 | + * @return IMountPoint[] |
|
| 105 | + */ |
|
| 106 | + public function getMountsForUser(IUser $user, IStorageFactory $loader) { |
|
| 107 | + $this->userStoragesService->setUser($user); |
|
| 108 | + $this->userGlobalStoragesService->setUser($user); |
|
| 109 | + |
|
| 110 | + $storageConfigs = $this->userGlobalStoragesService->getAllStoragesForUser(); |
|
| 111 | + |
|
| 112 | + $storages = array_map(function (StorageConfig $storageConfig) use ($user) { |
|
| 113 | + try { |
|
| 114 | + return $this->constructStorageForUser($user, $storageConfig); |
|
| 115 | + } catch (\Exception $e) { |
|
| 116 | + // propagate exception into filesystem |
|
| 117 | + return new FailedStorage(['exception' => $e]); |
|
| 118 | + } |
|
| 119 | + }, $storageConfigs); |
|
| 120 | + |
|
| 121 | + |
|
| 122 | + Storage::getGlobalCache()->loadForStorageIds(array_map(function (IStorage $storage) { |
|
| 123 | + return $storage->getId(); |
|
| 124 | + }, $storages)); |
|
| 125 | + |
|
| 126 | + $availableStorages = array_map(function (IStorage $storage, StorageConfig $storageConfig): IStorage { |
|
| 127 | + try { |
|
| 128 | + $availability = $storage->getAvailability(); |
|
| 129 | + if (!$availability['available'] && !Availability::shouldRecheck($availability)) { |
|
| 130 | + $storage = new FailedStorage([ |
|
| 131 | + 'exception' => new StorageNotAvailableException('Storage with mount id ' . $storageConfig->getId() . ' is not available'), |
|
| 132 | + ]); |
|
| 133 | + } |
|
| 134 | + } catch (\Exception $e) { |
|
| 135 | + // propagate exception into filesystem |
|
| 136 | + $storage = new FailedStorage(['exception' => $e]); |
|
| 137 | + } |
|
| 138 | + return $storage; |
|
| 139 | + }, $storages, $storageConfigs); |
|
| 140 | + |
|
| 141 | + $mounts = array_map(function (StorageConfig $storageConfig, IStorage $storage) use ($user, $loader) { |
|
| 142 | + $storage->setOwner($user->getUID()); |
|
| 143 | + if ($storageConfig->getType() === StorageConfig::MOUNT_TYPE_PERSONAL) { |
|
| 144 | + return new PersonalMount( |
|
| 145 | + $this->userStoragesService, |
|
| 146 | + $storageConfig, |
|
| 147 | + $storageConfig->getId(), |
|
| 148 | + new KnownMtime([ |
|
| 149 | + 'storage' => $storage, |
|
| 150 | + 'clock' => $this->clock, |
|
| 151 | + ]), |
|
| 152 | + '/' . $user->getUID() . '/files' . $storageConfig->getMountPoint(), |
|
| 153 | + null, |
|
| 154 | + $loader, |
|
| 155 | + $storageConfig->getMountOptions(), |
|
| 156 | + $storageConfig->getId(), |
|
| 157 | + ); |
|
| 158 | + } else { |
|
| 159 | + return new SystemMountPoint( |
|
| 160 | + $storageConfig, |
|
| 161 | + $storage, |
|
| 162 | + '/' . $user->getUID() . '/files' . $storageConfig->getMountPoint(), |
|
| 163 | + null, |
|
| 164 | + $loader, |
|
| 165 | + $storageConfig->getMountOptions(), |
|
| 166 | + $storageConfig->getId(), |
|
| 167 | + ); |
|
| 168 | + } |
|
| 169 | + }, $storageConfigs, $availableStorages); |
|
| 170 | + |
|
| 171 | + $this->userStoragesService->resetUser(); |
|
| 172 | + $this->userGlobalStoragesService->resetUser(); |
|
| 173 | + |
|
| 174 | + return $mounts; |
|
| 175 | + } |
|
| 176 | 176 | } |
@@ -59,7 +59,7 @@ discard block |
||
| 59 | 59 | * |
| 60 | 60 | * @throws ContainerExceptionInterface |
| 61 | 61 | */ |
| 62 | - private function prepareStorageConfig(StorageConfig &$storage, IUser $user): void { |
|
| 62 | + private function prepareStorageConfig(StorageConfig & $storage, IUser $user): void { |
|
| 63 | 63 | foreach ($storage->getBackendOptions() as $option => $value) { |
| 64 | 64 | $storage->setBackendOption($option, MountConfig::substitutePlaceholdersInConfig($value, $user->getUID())); |
| 65 | 65 | } |
@@ -109,7 +109,7 @@ discard block |
||
| 109 | 109 | |
| 110 | 110 | $storageConfigs = $this->userGlobalStoragesService->getAllStoragesForUser(); |
| 111 | 111 | |
| 112 | - $storages = array_map(function (StorageConfig $storageConfig) use ($user) { |
|
| 112 | + $storages = array_map(function(StorageConfig $storageConfig) use ($user) { |
|
| 113 | 113 | try { |
| 114 | 114 | return $this->constructStorageForUser($user, $storageConfig); |
| 115 | 115 | } catch (\Exception $e) { |
@@ -119,16 +119,16 @@ discard block |
||
| 119 | 119 | }, $storageConfigs); |
| 120 | 120 | |
| 121 | 121 | |
| 122 | - Storage::getGlobalCache()->loadForStorageIds(array_map(function (IStorage $storage) { |
|
| 122 | + Storage::getGlobalCache()->loadForStorageIds(array_map(function(IStorage $storage) { |
|
| 123 | 123 | return $storage->getId(); |
| 124 | 124 | }, $storages)); |
| 125 | 125 | |
| 126 | - $availableStorages = array_map(function (IStorage $storage, StorageConfig $storageConfig): IStorage { |
|
| 126 | + $availableStorages = array_map(function(IStorage $storage, StorageConfig $storageConfig): IStorage { |
|
| 127 | 127 | try { |
| 128 | 128 | $availability = $storage->getAvailability(); |
| 129 | 129 | if (!$availability['available'] && !Availability::shouldRecheck($availability)) { |
| 130 | 130 | $storage = new FailedStorage([ |
| 131 | - 'exception' => new StorageNotAvailableException('Storage with mount id ' . $storageConfig->getId() . ' is not available'), |
|
| 131 | + 'exception' => new StorageNotAvailableException('Storage with mount id '.$storageConfig->getId().' is not available'), |
|
| 132 | 132 | ]); |
| 133 | 133 | } |
| 134 | 134 | } catch (\Exception $e) { |
@@ -138,7 +138,7 @@ discard block |
||
| 138 | 138 | return $storage; |
| 139 | 139 | }, $storages, $storageConfigs); |
| 140 | 140 | |
| 141 | - $mounts = array_map(function (StorageConfig $storageConfig, IStorage $storage) use ($user, $loader) { |
|
| 141 | + $mounts = array_map(function(StorageConfig $storageConfig, IStorage $storage) use ($user, $loader) { |
|
| 142 | 142 | $storage->setOwner($user->getUID()); |
| 143 | 143 | if ($storageConfig->getType() === StorageConfig::MOUNT_TYPE_PERSONAL) { |
| 144 | 144 | return new PersonalMount( |
@@ -149,7 +149,7 @@ discard block |
||
| 149 | 149 | 'storage' => $storage, |
| 150 | 150 | 'clock' => $this->clock, |
| 151 | 151 | ]), |
| 152 | - '/' . $user->getUID() . '/files' . $storageConfig->getMountPoint(), |
|
| 152 | + '/'.$user->getUID().'/files'.$storageConfig->getMountPoint(), |
|
| 153 | 153 | null, |
| 154 | 154 | $loader, |
| 155 | 155 | $storageConfig->getMountOptions(), |
@@ -159,7 +159,7 @@ discard block |
||
| 159 | 159 | return new SystemMountPoint( |
| 160 | 160 | $storageConfig, |
| 161 | 161 | $storage, |
| 162 | - '/' . $user->getUID() . '/files' . $storageConfig->getMountPoint(), |
|
| 162 | + '/'.$user->getUID().'/files'.$storageConfig->getMountPoint(), |
|
| 163 | 163 | null, |
| 164 | 164 | $loader, |
| 165 | 165 | $storageConfig->getMountOptions(), |
@@ -65,102 +65,102 @@ |
||
| 65 | 65 | * @package OCA\Files_External\AppInfo |
| 66 | 66 | */ |
| 67 | 67 | class Application extends App implements IBackendProvider, IAuthMechanismProvider, IBootstrap { |
| 68 | - public const APP_ID = 'files_external'; |
|
| 69 | - |
|
| 70 | - /** |
|
| 71 | - * Application constructor. |
|
| 72 | - * |
|
| 73 | - * @throws ContainerExceptionInterface |
|
| 74 | - */ |
|
| 75 | - public function __construct(array $urlParams = []) { |
|
| 76 | - parent::__construct(self::APP_ID, $urlParams); |
|
| 77 | - } |
|
| 78 | - |
|
| 79 | - public function register(IRegistrationContext $context): void { |
|
| 80 | - $context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); |
|
| 81 | - $context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class); |
|
| 82 | - $context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class); |
|
| 83 | - $context->registerEventListener(StorageCreatedEvent::class, MountCacheService::class); |
|
| 84 | - $context->registerEventListener(StorageDeletedEvent::class, MountCacheService::class); |
|
| 85 | - $context->registerEventListener(StorageUpdatedEvent::class, MountCacheService::class); |
|
| 86 | - $context->registerEventListener(BeforeGroupDeletedEvent::class, MountCacheService::class); |
|
| 87 | - $context->registerEventListener(UserCreatedEvent::class, MountCacheService::class); |
|
| 88 | - $context->registerEventListener(UserAddedEvent::class, MountCacheService::class); |
|
| 89 | - $context->registerEventListener(UserRemovedEvent::class, MountCacheService::class); |
|
| 90 | - $context->registerEventListener(PostLoginEvent::class, MountCacheService::class); |
|
| 91 | - |
|
| 92 | - $context->registerConfigLexicon(ConfigLexicon::class); |
|
| 93 | - } |
|
| 94 | - |
|
| 95 | - public function boot(IBootContext $context): void { |
|
| 96 | - $context->injectFn(function (IMountProviderCollection $mountProviderCollection, ConfigAdapter $configAdapter): void { |
|
| 97 | - $mountProviderCollection->registerProvider($configAdapter); |
|
| 98 | - }); |
|
| 99 | - $context->injectFn(function (BackendService $backendService, UserPlaceholderHandler $userConfigHandler): void { |
|
| 100 | - $backendService->registerBackendProvider($this); |
|
| 101 | - $backendService->registerAuthMechanismProvider($this); |
|
| 102 | - $backendService->registerConfigHandler('user', function () use ($userConfigHandler) { |
|
| 103 | - return $userConfigHandler; |
|
| 104 | - }); |
|
| 105 | - }); |
|
| 106 | - } |
|
| 107 | - |
|
| 108 | - /** |
|
| 109 | - * @{inheritdoc} |
|
| 110 | - */ |
|
| 111 | - public function getBackends() { |
|
| 112 | - $container = $this->getContainer(); |
|
| 113 | - |
|
| 114 | - $backends = [ |
|
| 115 | - $container->get(Local::class), |
|
| 116 | - $container->get(FTP::class), |
|
| 117 | - $container->get(DAV::class), |
|
| 118 | - $container->get(OwnCloud::class), |
|
| 119 | - $container->get(SFTP::class), |
|
| 120 | - $container->get(AmazonS3::class), |
|
| 121 | - $container->get(Swift::class), |
|
| 122 | - $container->get(SFTP_Key::class), |
|
| 123 | - $container->get(SMB::class), |
|
| 124 | - $container->get(SMB_OC::class), |
|
| 125 | - ]; |
|
| 126 | - |
|
| 127 | - return $backends; |
|
| 128 | - } |
|
| 129 | - |
|
| 130 | - /** |
|
| 131 | - * @{inheritdoc} |
|
| 132 | - */ |
|
| 133 | - public function getAuthMechanisms() { |
|
| 134 | - $container = $this->getContainer(); |
|
| 135 | - |
|
| 136 | - return [ |
|
| 137 | - // AuthMechanism::SCHEME_NULL mechanism |
|
| 138 | - $container->get(NullMechanism::class), |
|
| 139 | - |
|
| 140 | - // AuthMechanism::SCHEME_BUILTIN mechanism |
|
| 141 | - $container->get(Builtin::class), |
|
| 142 | - |
|
| 143 | - // AuthMechanism::SCHEME_PASSWORD mechanisms |
|
| 144 | - $container->get(Password::class), |
|
| 145 | - $container->get(SessionCredentials::class), |
|
| 146 | - $container->get(LoginCredentials::class), |
|
| 147 | - $container->get(UserProvided::class), |
|
| 148 | - $container->get(GlobalAuth::class), |
|
| 149 | - $container->get(UserGlobalAuth::class), |
|
| 150 | - |
|
| 151 | - // AuthMechanism::SCHEME_PUBLICKEY mechanisms |
|
| 152 | - $container->get(RSA::class), |
|
| 153 | - $container->get(RSAPrivateKey::class), |
|
| 154 | - |
|
| 155 | - // AuthMechanism::SCHEME_OPENSTACK mechanisms |
|
| 156 | - $container->get(OpenStackV2::class), |
|
| 157 | - $container->get(OpenStackV3::class), |
|
| 158 | - $container->get(Rackspace::class), |
|
| 159 | - |
|
| 160 | - // Specialized mechanisms |
|
| 161 | - $container->get(AccessKey::class), |
|
| 162 | - $container->get(KerberosAuth::class), |
|
| 163 | - $container->get(KerberosApacheAuth::class), |
|
| 164 | - ]; |
|
| 165 | - } |
|
| 68 | + public const APP_ID = 'files_external'; |
|
| 69 | + |
|
| 70 | + /** |
|
| 71 | + * Application constructor. |
|
| 72 | + * |
|
| 73 | + * @throws ContainerExceptionInterface |
|
| 74 | + */ |
|
| 75 | + public function __construct(array $urlParams = []) { |
|
| 76 | + parent::__construct(self::APP_ID, $urlParams); |
|
| 77 | + } |
|
| 78 | + |
|
| 79 | + public function register(IRegistrationContext $context): void { |
|
| 80 | + $context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); |
|
| 81 | + $context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class); |
|
| 82 | + $context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class); |
|
| 83 | + $context->registerEventListener(StorageCreatedEvent::class, MountCacheService::class); |
|
| 84 | + $context->registerEventListener(StorageDeletedEvent::class, MountCacheService::class); |
|
| 85 | + $context->registerEventListener(StorageUpdatedEvent::class, MountCacheService::class); |
|
| 86 | + $context->registerEventListener(BeforeGroupDeletedEvent::class, MountCacheService::class); |
|
| 87 | + $context->registerEventListener(UserCreatedEvent::class, MountCacheService::class); |
|
| 88 | + $context->registerEventListener(UserAddedEvent::class, MountCacheService::class); |
|
| 89 | + $context->registerEventListener(UserRemovedEvent::class, MountCacheService::class); |
|
| 90 | + $context->registerEventListener(PostLoginEvent::class, MountCacheService::class); |
|
| 91 | + |
|
| 92 | + $context->registerConfigLexicon(ConfigLexicon::class); |
|
| 93 | + } |
|
| 94 | + |
|
| 95 | + public function boot(IBootContext $context): void { |
|
| 96 | + $context->injectFn(function (IMountProviderCollection $mountProviderCollection, ConfigAdapter $configAdapter): void { |
|
| 97 | + $mountProviderCollection->registerProvider($configAdapter); |
|
| 98 | + }); |
|
| 99 | + $context->injectFn(function (BackendService $backendService, UserPlaceholderHandler $userConfigHandler): void { |
|
| 100 | + $backendService->registerBackendProvider($this); |
|
| 101 | + $backendService->registerAuthMechanismProvider($this); |
|
| 102 | + $backendService->registerConfigHandler('user', function () use ($userConfigHandler) { |
|
| 103 | + return $userConfigHandler; |
|
| 104 | + }); |
|
| 105 | + }); |
|
| 106 | + } |
|
| 107 | + |
|
| 108 | + /** |
|
| 109 | + * @{inheritdoc} |
|
| 110 | + */ |
|
| 111 | + public function getBackends() { |
|
| 112 | + $container = $this->getContainer(); |
|
| 113 | + |
|
| 114 | + $backends = [ |
|
| 115 | + $container->get(Local::class), |
|
| 116 | + $container->get(FTP::class), |
|
| 117 | + $container->get(DAV::class), |
|
| 118 | + $container->get(OwnCloud::class), |
|
| 119 | + $container->get(SFTP::class), |
|
| 120 | + $container->get(AmazonS3::class), |
|
| 121 | + $container->get(Swift::class), |
|
| 122 | + $container->get(SFTP_Key::class), |
|
| 123 | + $container->get(SMB::class), |
|
| 124 | + $container->get(SMB_OC::class), |
|
| 125 | + ]; |
|
| 126 | + |
|
| 127 | + return $backends; |
|
| 128 | + } |
|
| 129 | + |
|
| 130 | + /** |
|
| 131 | + * @{inheritdoc} |
|
| 132 | + */ |
|
| 133 | + public function getAuthMechanisms() { |
|
| 134 | + $container = $this->getContainer(); |
|
| 135 | + |
|
| 136 | + return [ |
|
| 137 | + // AuthMechanism::SCHEME_NULL mechanism |
|
| 138 | + $container->get(NullMechanism::class), |
|
| 139 | + |
|
| 140 | + // AuthMechanism::SCHEME_BUILTIN mechanism |
|
| 141 | + $container->get(Builtin::class), |
|
| 142 | + |
|
| 143 | + // AuthMechanism::SCHEME_PASSWORD mechanisms |
|
| 144 | + $container->get(Password::class), |
|
| 145 | + $container->get(SessionCredentials::class), |
|
| 146 | + $container->get(LoginCredentials::class), |
|
| 147 | + $container->get(UserProvided::class), |
|
| 148 | + $container->get(GlobalAuth::class), |
|
| 149 | + $container->get(UserGlobalAuth::class), |
|
| 150 | + |
|
| 151 | + // AuthMechanism::SCHEME_PUBLICKEY mechanisms |
|
| 152 | + $container->get(RSA::class), |
|
| 153 | + $container->get(RSAPrivateKey::class), |
|
| 154 | + |
|
| 155 | + // AuthMechanism::SCHEME_OPENSTACK mechanisms |
|
| 156 | + $container->get(OpenStackV2::class), |
|
| 157 | + $container->get(OpenStackV3::class), |
|
| 158 | + $container->get(Rackspace::class), |
|
| 159 | + |
|
| 160 | + // Specialized mechanisms |
|
| 161 | + $container->get(AccessKey::class), |
|
| 162 | + $container->get(KerberosAuth::class), |
|
| 163 | + $container->get(KerberosApacheAuth::class), |
|
| 164 | + ]; |
|
| 165 | + } |
|
| 166 | 166 | } |
@@ -18,91 +18,91 @@ |
||
| 18 | 18 | use OCP\IUser; |
| 19 | 19 | |
| 20 | 20 | class ResourceProvider implements IProvider { |
| 21 | - public const RESOURCE_TYPE = 'file'; |
|
| 21 | + public const RESOURCE_TYPE = 'file'; |
|
| 22 | 22 | |
| 23 | - protected array $nodes = []; |
|
| 23 | + protected array $nodes = []; |
|
| 24 | 24 | |
| 25 | - public function __construct( |
|
| 26 | - protected IRootFolder $rootFolder, |
|
| 27 | - private IPreview $preview, |
|
| 28 | - private IURLGenerator $urlGenerator, |
|
| 29 | - ) { |
|
| 30 | - } |
|
| 25 | + public function __construct( |
|
| 26 | + protected IRootFolder $rootFolder, |
|
| 27 | + private IPreview $preview, |
|
| 28 | + private IURLGenerator $urlGenerator, |
|
| 29 | + ) { |
|
| 30 | + } |
|
| 31 | 31 | |
| 32 | - private function getNode(IResource $resource): ?Node { |
|
| 33 | - if (isset($this->nodes[(int)$resource->getId()])) { |
|
| 34 | - return $this->nodes[(int)$resource->getId()]; |
|
| 35 | - } |
|
| 36 | - $node = $this->rootFolder->getFirstNodeById((int)$resource->getId()); |
|
| 37 | - if ($node) { |
|
| 38 | - $this->nodes[(int)$resource->getId()] = $node; |
|
| 39 | - return $this->nodes[(int)$resource->getId()]; |
|
| 40 | - } |
|
| 41 | - return null; |
|
| 42 | - } |
|
| 32 | + private function getNode(IResource $resource): ?Node { |
|
| 33 | + if (isset($this->nodes[(int)$resource->getId()])) { |
|
| 34 | + return $this->nodes[(int)$resource->getId()]; |
|
| 35 | + } |
|
| 36 | + $node = $this->rootFolder->getFirstNodeById((int)$resource->getId()); |
|
| 37 | + if ($node) { |
|
| 38 | + $this->nodes[(int)$resource->getId()] = $node; |
|
| 39 | + return $this->nodes[(int)$resource->getId()]; |
|
| 40 | + } |
|
| 41 | + return null; |
|
| 42 | + } |
|
| 43 | 43 | |
| 44 | - /** |
|
| 45 | - * @param IResource $resource |
|
| 46 | - * @return array |
|
| 47 | - * @since 16.0.0 |
|
| 48 | - */ |
|
| 49 | - public function getResourceRichObject(IResource $resource): array { |
|
| 50 | - if (isset($this->nodes[(int)$resource->getId()])) { |
|
| 51 | - $node = $this->nodes[(int)$resource->getId()]->getPath(); |
|
| 52 | - } else { |
|
| 53 | - $node = $this->getNode($resource); |
|
| 54 | - } |
|
| 44 | + /** |
|
| 45 | + * @param IResource $resource |
|
| 46 | + * @return array |
|
| 47 | + * @since 16.0.0 |
|
| 48 | + */ |
|
| 49 | + public function getResourceRichObject(IResource $resource): array { |
|
| 50 | + if (isset($this->nodes[(int)$resource->getId()])) { |
|
| 51 | + $node = $this->nodes[(int)$resource->getId()]->getPath(); |
|
| 52 | + } else { |
|
| 53 | + $node = $this->getNode($resource); |
|
| 54 | + } |
|
| 55 | 55 | |
| 56 | - if ($node instanceof Node) { |
|
| 57 | - $link = $this->urlGenerator->linkToRouteAbsolute( |
|
| 58 | - 'files.viewcontroller.showFile', |
|
| 59 | - ['fileid' => $resource->getId()] |
|
| 60 | - ); |
|
| 61 | - return [ |
|
| 62 | - 'type' => 'file', |
|
| 63 | - 'id' => $resource->getId(), |
|
| 64 | - 'name' => $node->getName(), |
|
| 65 | - 'path' => $node->getInternalPath(), |
|
| 66 | - 'link' => $link, |
|
| 67 | - 'mimetype' => $node->getMimetype(), |
|
| 68 | - 'preview-available' => $this->preview->isAvailable($node), |
|
| 69 | - ]; |
|
| 70 | - } |
|
| 56 | + if ($node instanceof Node) { |
|
| 57 | + $link = $this->urlGenerator->linkToRouteAbsolute( |
|
| 58 | + 'files.viewcontroller.showFile', |
|
| 59 | + ['fileid' => $resource->getId()] |
|
| 60 | + ); |
|
| 61 | + return [ |
|
| 62 | + 'type' => 'file', |
|
| 63 | + 'id' => $resource->getId(), |
|
| 64 | + 'name' => $node->getName(), |
|
| 65 | + 'path' => $node->getInternalPath(), |
|
| 66 | + 'link' => $link, |
|
| 67 | + 'mimetype' => $node->getMimetype(), |
|
| 68 | + 'preview-available' => $this->preview->isAvailable($node), |
|
| 69 | + ]; |
|
| 70 | + } |
|
| 71 | 71 | |
| 72 | - throw new ResourceException('File not found'); |
|
| 73 | - } |
|
| 72 | + throw new ResourceException('File not found'); |
|
| 73 | + } |
|
| 74 | 74 | |
| 75 | - /** |
|
| 76 | - * Can a user/guest access the collection |
|
| 77 | - * |
|
| 78 | - * @param IResource $resource |
|
| 79 | - * @param IUser $user |
|
| 80 | - * @return bool |
|
| 81 | - * @since 16.0.0 |
|
| 82 | - */ |
|
| 83 | - public function canAccessResource(IResource $resource, ?IUser $user = null): bool { |
|
| 84 | - if (!$user instanceof IUser) { |
|
| 85 | - return false; |
|
| 86 | - } |
|
| 75 | + /** |
|
| 76 | + * Can a user/guest access the collection |
|
| 77 | + * |
|
| 78 | + * @param IResource $resource |
|
| 79 | + * @param IUser $user |
|
| 80 | + * @return bool |
|
| 81 | + * @since 16.0.0 |
|
| 82 | + */ |
|
| 83 | + public function canAccessResource(IResource $resource, ?IUser $user = null): bool { |
|
| 84 | + if (!$user instanceof IUser) { |
|
| 85 | + return false; |
|
| 86 | + } |
|
| 87 | 87 | |
| 88 | - $userFolder = $this->rootFolder->getUserFolder($user->getUID()); |
|
| 89 | - $node = $userFolder->getById((int)$resource->getId()); |
|
| 88 | + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); |
|
| 89 | + $node = $userFolder->getById((int)$resource->getId()); |
|
| 90 | 90 | |
| 91 | - if ($node) { |
|
| 92 | - $this->nodes[(int)$resource->getId()] = $node; |
|
| 93 | - return true; |
|
| 94 | - } |
|
| 91 | + if ($node) { |
|
| 92 | + $this->nodes[(int)$resource->getId()] = $node; |
|
| 93 | + return true; |
|
| 94 | + } |
|
| 95 | 95 | |
| 96 | - return false; |
|
| 97 | - } |
|
| 96 | + return false; |
|
| 97 | + } |
|
| 98 | 98 | |
| 99 | - /** |
|
| 100 | - * Get the resource type of the provider |
|
| 101 | - * |
|
| 102 | - * @return string |
|
| 103 | - * @since 16.0.0 |
|
| 104 | - */ |
|
| 105 | - public function getType(): string { |
|
| 106 | - return self::RESOURCE_TYPE; |
|
| 107 | - } |
|
| 99 | + /** |
|
| 100 | + * Get the resource type of the provider |
|
| 101 | + * |
|
| 102 | + * @return string |
|
| 103 | + * @since 16.0.0 |
|
| 104 | + */ |
|
| 105 | + public function getType(): string { |
|
| 106 | + return self::RESOURCE_TYPE; |
|
| 107 | + } |
|
| 108 | 108 | } |