CloudFederationProviderManager.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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', [
  98. 'body' => json_encode($share->getShare()),
  99. 'headers' => ['content-type' => 'application/json'],
  100. 'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
  101. 'timeout' => 10,
  102. 'connect_timeout' => 10,
  103. ]);
  104. if ($response->getStatusCode() === Http::STATUS_CREATED) {
  105. $result = json_decode($response->getBody(), true);
  106. return (is_array($result)) ? $result : [];
  107. }
  108. } catch (\Exception $e) {
  109. $this->logger->debug($e->getMessage(), ['exception' => $e]);
  110. // if flat re-sharing is not supported by the remote server
  111. // we re-throw the exception and fall back to the old behaviour.
  112. // (flat re-shares has been introduced in Nextcloud 9.1)
  113. if ($e->getCode() === Http::STATUS_INTERNAL_SERVER_ERROR) {
  114. throw $e;
  115. }
  116. }
  117. return false;
  118. }
  119. /**
  120. * @param ICloudFederationShare $share
  121. * @return IResponse
  122. * @throws OCMProviderException
  123. */
  124. public function sendCloudShare(ICloudFederationShare $share): IResponse {
  125. $cloudID = $this->cloudIdManager->resolveCloudId($share->getShareWith());
  126. $ocmProvider = $this->discoveryService->discover($cloudID->getRemote());
  127. $client = $this->httpClientService->newClient();
  128. try {
  129. return $client->post($ocmProvider->getEndPoint() . '/shares', [
  130. 'body' => json_encode($share->getShare()),
  131. 'headers' => ['content-type' => 'application/json'],
  132. 'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
  133. 'timeout' => 10,
  134. 'connect_timeout' => 10,
  135. ]);
  136. } catch (\Throwable $e) {
  137. $this->logger->error('Error while sending share to federation server: ' . $e->getMessage(), ['exception' => $e]);
  138. try {
  139. return $client->getResponseFromThrowable($e);
  140. } catch (\Throwable $e) {
  141. throw new OCMProviderException($e->getMessage(), $e->getCode(), $e);
  142. }
  143. }
  144. }
  145. /**
  146. * @param string $url
  147. * @param ICloudFederationNotification $notification
  148. * @return array|false
  149. * @deprecated 29.0.0 - Use {@see sendCloudNotification()} instead and handle errors manually
  150. */
  151. public function sendNotification($url, ICloudFederationNotification $notification) {
  152. try {
  153. $ocmProvider = $this->discoveryService->discover($url);
  154. } catch (OCMProviderException $e) {
  155. return false;
  156. }
  157. $client = $this->httpClientService->newClient();
  158. try {
  159. $response = $client->post($ocmProvider->getEndPoint() . '/notifications', [
  160. 'body' => json_encode($notification->getMessage()),
  161. 'headers' => ['content-type' => 'application/json'],
  162. 'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
  163. 'timeout' => 10,
  164. 'connect_timeout' => 10,
  165. ]);
  166. if ($response->getStatusCode() === Http::STATUS_CREATED) {
  167. $result = json_decode($response->getBody(), true);
  168. return (is_array($result)) ? $result : [];
  169. }
  170. } catch (\Exception $e) {
  171. // log the error and return false
  172. $this->logger->error('error while sending notification for federated share: ' . $e->getMessage(), ['exception' => $e]);
  173. }
  174. return false;
  175. }
  176. /**
  177. * @param string $url
  178. * @param ICloudFederationNotification $notification
  179. * @return IResponse
  180. * @throws OCMProviderException
  181. */
  182. public function sendCloudNotification(string $url, ICloudFederationNotification $notification): IResponse {
  183. $ocmProvider = $this->discoveryService->discover($url);
  184. $client = $this->httpClientService->newClient();
  185. try {
  186. return $client->post($ocmProvider->getEndPoint() . '/notifications', [
  187. 'body' => json_encode($notification->getMessage()),
  188. 'headers' => ['content-type' => 'application/json'],
  189. 'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
  190. 'timeout' => 10,
  191. 'connect_timeout' => 10,
  192. ]);
  193. } catch (\Throwable $e) {
  194. $this->logger->error('Error while sending notification to federation server: ' . $e->getMessage(), ['exception' => $e]);
  195. try {
  196. return $client->getResponseFromThrowable($e);
  197. } catch (\Throwable $e) {
  198. throw new OCMProviderException($e->getMessage(), $e->getCode(), $e);
  199. }
  200. }
  201. }
  202. /**
  203. * check if the new cloud federation API is ready to be used
  204. *
  205. * @return bool
  206. */
  207. public function isReady() {
  208. return $this->appManager->isEnabledForUser('cloud_federation_api');
  209. }
  210. }