CloudFederationProviderManager.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OC\Federation;
  8. use OC\AppFramework\Http;
  9. use OCP\App\IAppManager;
  10. use OCP\Federation\Exceptions\ProviderDoesNotExistsException;
  11. use OCP\Federation\ICloudFederationNotification;
  12. use OCP\Federation\ICloudFederationProvider;
  13. use OCP\Federation\ICloudFederationProviderManager;
  14. use OCP\Federation\ICloudFederationShare;
  15. use OCP\Federation\ICloudIdManager;
  16. use OCP\Http\Client\IClientService;
  17. use OCP\Http\Client\IResponse;
  18. use OCP\IConfig;
  19. use OCP\OCM\Exceptions\OCMProviderException;
  20. use OCP\OCM\IOCMDiscoveryService;
  21. use Psr\Log\LoggerInterface;
  22. /**
  23. * Class Manager
  24. *
  25. * Manage Cloud Federation Providers
  26. *
  27. * @package OC\Federation
  28. */
  29. class CloudFederationProviderManager implements ICloudFederationProviderManager {
  30. /** @var array list of available cloud federation providers */
  31. private array $cloudFederationProvider = [];
  32. public function __construct(
  33. private IConfig $config,
  34. private IAppManager $appManager,
  35. private IClientService $httpClientService,
  36. private ICloudIdManager $cloudIdManager,
  37. private IOCMDiscoveryService $discoveryService,
  38. private LoggerInterface $logger,
  39. ) {
  40. }
  41. /**
  42. * Registers an callback function which must return an cloud federation provider
  43. *
  44. * @param string $resourceType which resource type does the provider handles
  45. * @param string $displayName user facing name of the federated share provider
  46. * @param callable $callback
  47. */
  48. public function addCloudFederationProvider($resourceType, $displayName, callable $callback) {
  49. $this->cloudFederationProvider[$resourceType] = [
  50. 'resourceType' => $resourceType,
  51. 'displayName' => $displayName,
  52. 'callback' => $callback,
  53. ];
  54. }
  55. /**
  56. * remove cloud federation provider
  57. *
  58. * @param string $providerId
  59. */
  60. public function removeCloudFederationProvider($providerId) {
  61. unset($this->cloudFederationProvider[$providerId]);
  62. }
  63. /**
  64. * get a list of all cloudFederationProviders
  65. *
  66. * @return array [resourceType => ['resourceType' => $resourceType, 'displayName' => $displayName, 'callback' => callback]]
  67. */
  68. public function getAllCloudFederationProviders() {
  69. return $this->cloudFederationProvider;
  70. }
  71. /**
  72. * get a specific cloud federation provider
  73. *
  74. * @param string $resourceType
  75. * @return ICloudFederationProvider
  76. * @throws ProviderDoesNotExistsException
  77. */
  78. public function getCloudFederationProvider($resourceType) {
  79. if (isset($this->cloudFederationProvider[$resourceType])) {
  80. return call_user_func($this->cloudFederationProvider[$resourceType]['callback']);
  81. } else {
  82. throw new ProviderDoesNotExistsException($resourceType);
  83. }
  84. }
  85. /**
  86. * @deprecated 29.0.0 - Use {@see sendCloudShare()} instead and handle errors manually
  87. */
  88. public function sendShare(ICloudFederationShare $share) {
  89. $cloudID = $this->cloudIdManager->resolveCloudId($share->getShareWith());
  90. try {
  91. $ocmProvider = $this->discoveryService->discover($cloudID->getRemote());
  92. } catch (OCMProviderException $e) {
  93. return false;
  94. }
  95. $client = $this->httpClientService->newClient();
  96. try {
  97. $response = $client->post($ocmProvider->getEndPoint() . '/shares', array_merge($this->getDefaultRequestOptions(), [
  98. 'body' => json_encode($share->getShare()),
  99. ]));
  100. if ($response->getStatusCode() === Http::STATUS_CREATED) {
  101. $result = json_decode($response->getBody(), true);
  102. return (is_array($result)) ? $result : [];
  103. }
  104. } catch (\Exception $e) {
  105. $this->logger->debug($e->getMessage(), ['exception' => $e]);
  106. // if flat re-sharing is not supported by the remote server
  107. // we re-throw the exception and fall back to the old behaviour.
  108. // (flat re-shares has been introduced in Nextcloud 9.1)
  109. if ($e->getCode() === Http::STATUS_INTERNAL_SERVER_ERROR) {
  110. throw $e;
  111. }
  112. }
  113. return false;
  114. }
  115. /**
  116. * @param ICloudFederationShare $share
  117. * @return IResponse
  118. * @throws OCMProviderException
  119. */
  120. public function sendCloudShare(ICloudFederationShare $share): IResponse {
  121. $cloudID = $this->cloudIdManager->resolveCloudId($share->getShareWith());
  122. $ocmProvider = $this->discoveryService->discover($cloudID->getRemote());
  123. $client = $this->httpClientService->newClient();
  124. try {
  125. return $client->post($ocmProvider->getEndPoint() . '/shares', array_merge($this->getDefaultRequestOptions(), [
  126. 'body' => json_encode($share->getShare()),
  127. ]));
  128. } catch (\Throwable $e) {
  129. $this->logger->error('Error while sending share to federation server: ' . $e->getMessage(), ['exception' => $e]);
  130. try {
  131. return $client->getResponseFromThrowable($e);
  132. } catch (\Throwable $e) {
  133. throw new OCMProviderException($e->getMessage(), $e->getCode(), $e);
  134. }
  135. }
  136. }
  137. /**
  138. * @param string $url
  139. * @param ICloudFederationNotification $notification
  140. * @return array|false
  141. * @deprecated 29.0.0 - Use {@see sendCloudNotification()} instead and handle errors manually
  142. */
  143. public function sendNotification($url, ICloudFederationNotification $notification) {
  144. try {
  145. $ocmProvider = $this->discoveryService->discover($url);
  146. } catch (OCMProviderException $e) {
  147. return false;
  148. }
  149. $client = $this->httpClientService->newClient();
  150. try {
  151. $response = $client->post($ocmProvider->getEndPoint() . '/notifications', array_merge($this->getDefaultRequestOptions(), [
  152. 'body' => json_encode($notification->getMessage()),
  153. ]));
  154. if ($response->getStatusCode() === Http::STATUS_CREATED) {
  155. $result = json_decode($response->getBody(), true);
  156. return (is_array($result)) ? $result : [];
  157. }
  158. } catch (\Exception $e) {
  159. // log the error and return false
  160. $this->logger->error('error while sending notification for federated share: ' . $e->getMessage(), ['exception' => $e]);
  161. }
  162. return false;
  163. }
  164. /**
  165. * @param string $url
  166. * @param ICloudFederationNotification $notification
  167. * @return IResponse
  168. * @throws OCMProviderException
  169. */
  170. public function sendCloudNotification(string $url, ICloudFederationNotification $notification): IResponse {
  171. $ocmProvider = $this->discoveryService->discover($url);
  172. $client = $this->httpClientService->newClient();
  173. try {
  174. return $client->post($ocmProvider->getEndPoint() . '/notifications', array_merge($this->getDefaultRequestOptions(), [
  175. 'body' => json_encode($notification->getMessage()),
  176. ]));
  177. } catch (\Throwable $e) {
  178. $this->logger->error('Error while sending notification to federation server: ' . $e->getMessage(), ['exception' => $e]);
  179. try {
  180. return $client->getResponseFromThrowable($e);
  181. } catch (\Throwable $e) {
  182. throw new OCMProviderException($e->getMessage(), $e->getCode(), $e);
  183. }
  184. }
  185. }
  186. /**
  187. * check if the new cloud federation API is ready to be used
  188. *
  189. * @return bool
  190. */
  191. public function isReady() {
  192. return $this->appManager->isEnabledForUser('cloud_federation_api');
  193. }
  194. private function getDefaultRequestOptions(): array {
  195. $options = [
  196. 'headers' => ['content-type' => 'application/json'],
  197. 'timeout' => 10,
  198. 'connect_timeout' => 10,
  199. ];
  200. if ($this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates')) {
  201. $options['verify'] = false;
  202. }
  203. return $options;
  204. }
  205. }