nextcloud-swarm-plugin/lib/Storage/BeeSwarmTrait.php
Henry Bergström 63221db916
Release 20250307 (#114)
* Improving github and NC app store documentation (#102)

* fix(curl): upload SSL verification (#105)

- remove: verify parameters to default to curl
- add: only in dev env

* feat(upload): add filename as meta param when uploading (#103)

* Improve settings information (#104)

* Improve settings information

* feat(settings): add server host URL as default value

* style(settings): remove extra wordings

---------

Co-authored-by: Mahyar Iranibazaz <mahiarirani@pm.me>

* Feature: secure API communication upgrade (#107)

* feat(exception): throw separate exceptions

* feat(api): separate token from api link
- update: merge getLink into a single function
- add: Dto for Links results
- pass: token in authorization header
- update: upload and download to use new api links
- refactor: remove extra isVersion input

* chore: fix code style

---------

Co-authored-by: mahiarirani <10583381+mahiarirani@users.noreply.github.com>

* bugfix: exception handling to ensure the response is handled correctly (#109)

* bugfix: correct exception handling to ensure the response is handled correclty by calling test() function.
- update: do not assume an array (json) response from the api. The response is not always json which cause the json_decode() to return null;
- add: use StorageNotAvailableException to ensure a user-friendly error message to be displayed on the front.end;
- update: return value can be a string

* chore: fix code style

* feat(api): check status
- update: response based on status code
- add: specific error for invalid code

* chore: fix code style

---------

Co-authored-by: Take one <rontrevor@hotmail.com>
Co-authored-by: Mahyar Iranibazaz <mahiarirani@pm.me>
Co-authored-by: mahiarirani <10583381+mahiarirani@users.noreply.github.com>

* feat(docker): add install ocs api viewer app (#108)

* bugfix/correct-install-docker-windows (#106)

* - Correction to docker-compose for Windows installations.
- Added README for known issues

* Update README.md

bugfix(correct-install-docker-windows): add supporting images

* - add: formatting changes to README.md

---------

Co-authored-by: Take one <rontrevor@hotmail.com>

* Feature #1192 feedback form js (#111)

* Adding Feedback form

* Not working yet.
Probably need to change strategy and send request to nc first

* Feedback js working.
Something might be improved:
// TODO - Get API Url from beeswarmtrait or another place
// TODO - Improve layout with css
// TODO - Remove wiget when not is not in swarm folders

* chore: fix code style

* feat(env): upgrade get
- update: return null if key is not found

* feat(feedback): add api url
- add: app const
- add: env example

* feat(curl): add post and get methods
- update: swarm endpoints to use new methods

* feat(feedback): update feedback request

* refactor(curl): rename curl to request

* feat(curl): add accept headers to getLink
- refactor: use get for download instead of exec

* style(feedback): improve feedback from ui

* style(feedback): improve feedback from ui

* feat(feedback): use custom exception

* feat(feedback): add status code to exception

* feat(feedback): return correct status code

* feat(feedback): add feedback js as dependency
- remove: manual added js file
- add: npm package
- update: the code usage

---------

Co-authored-by: JoaoSRaposo <1598265+JoaoSRaposo@users.noreply.github.com>
Co-authored-by: Mahyar Iranibazaz <mahiarirani@pm.me>

* feat(curl): check url for protocol

* bugfix(feedback): remove the removed script load

* feat(toast): add nextcloud dialogs (#112)

- add: library package
- update: fileactions.js usage
- update: swarm logo remove xml

* Fix/#1085  adding moodle to the documentation (#100)

* Adding moodle documentation

* FIxing link formating  error

---------

Co-authored-by: JoaoSRaposo <joaosraposo@gmail.com>
Co-authored-by: Mahyar Iranibazaz <mahiarirani@pm.me>
Co-authored-by: mahiarirani <10583381+mahiarirani@users.noreply.github.com>
Co-authored-by: retrevor <75954541+retrevor@users.noreply.github.com>
Co-authored-by: Take one <rontrevor@hotmail.com>
Co-authored-by: JoaoSRaposo <1598265+JoaoSRaposo@users.noreply.github.com>
2025-03-07 16:44:03 +01:00

246 lines
7.2 KiB
PHP
Executable file

<?php
/**
* @copyright Copyright (c) 2022, MetaProvide Holding EKF
* @author Ron Trevor <ecoron@proton.me>
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace OCA\Files_External_Ethswarm\Storage;
use CURLFile;
use OCA\Files_External_Ethswarm\Auth\License;
use OCA\Files_External_Ethswarm\Backend\BeeSwarm;
use OCA\Files_External_Ethswarm\Dto\LinkDto;
use OCA\Files_External_Ethswarm\Exception\CurlException;
use OCA\Files_External_Ethswarm\Exception\HejBitException;
use OCA\Files_External_Ethswarm\Utils\Curl;
use OCP\Files\StorageBadConfigException;
use OCP\Files\StorageNotAvailableException;
trait BeeSwarmTrait {
private const INFRASTRUCTURE_VERSION_GATEWAY = 1;
private const INFRASTRUCTURE_VERSION_HEJBIT = 2;
protected string $api_url;
protected string $access_key;
public function isVersion(int $version = self::INFRASTRUCTURE_VERSION_GATEWAY): bool {
return match ($version) {
self::INFRASTRUCTURE_VERSION_GATEWAY => 'https://license.hejbit.com' === $this->api_url,
self::INFRASTRUCTURE_VERSION_HEJBIT => 'https://license.hejbit.com' !== $this->api_url,
default => false,
};
}
/**
* @throws StorageBadConfigException
*/
protected function parseParams(array $params): void {
$this->validateParams($params);
$this->api_url = $params[BeeSwarm::OPTION_HOST_URL];
$this->access_key = $params[License::SCHEME_ACCESS_KEY];
}
/**
* @throws StorageBadConfigException
*/
private function validateParams(array &$params): void {
if (!$params[BeeSwarm::OPTION_HOST_URL] || !$params[License::SCHEME_ACCESS_KEY]) {
throw new StorageBadConfigException('Creating '.self::class.' storage failed, required parameters not set');
}
if (!preg_match('/^https?:\/\//i', $params[BeeSwarm::OPTION_HOST_URL])) {
$params[BeeSwarm::OPTION_HOST_URL] = 'https://'.$params[BeeSwarm::OPTION_HOST_URL];
}
if (!filter_var($params[BeeSwarm::OPTION_HOST_URL], FILTER_VALIDATE_URL)) {
throw new StorageBadConfigException('Creating '.self::class.' storage failed, invalid url');
}
}
/**
* @throws CurlException|HejBitException
*/
private function getLink(string $endpoint): LinkDto {
$endpoint = $this->api_url.$endpoint;
$request = new Curl($endpoint, headers: [
'accept: application/json',
], authorization: $this->access_key);
$response = $request->get(true);
if (!$request->isResponseSuccessful()) {
throw new HejBitException('Failed to access HejBit: '.$response['message']);
}
return new LinkDto($response['url'], $response['token'], $response['method']);
}
/**
* @throws CurlException|HejBitException
*/
private function uploadSwarm(string $path, string $tempFile, string $mimetype): string {
if ($this->isVersion()) {
return $this->uploadSwarmV1($path, $tempFile, $mimetype);
}
$link = $this->getLink('/api/upload');
$request = new Curl($link->url, authorization: $link->token);
$response = $request->post([
'file' => new CURLFile($tempFile, $mimetype, basename($path)),
'name' => basename($path),
], true);
if (!$request->isResponseSuccessful() || !isset($response['reference'])) {
throw new HejBitException('Failed to upload file to HejBit: '.$response['message']);
}
return $response['reference'];
}
/**
* @return resource
*
* @throws CurlException|HejBitException
*/
private function downloadSwarm(string $reference) {
if ($this->isVersion()) {
return $this->downloadSwarmV1($reference);
}
$link = $this->getLink('/api/download');
$request = new Curl($link->url."/{$reference}", authorization: $link->token);
$response = $request->get();
if (!$request->isResponseSuccessful()) {
throw new HejBitException('Failed to download file from HejBit: '.$response['message']);
}
$stream = fopen('php://memory', 'r+');
fwrite($stream, $response);
rewind($stream);
return $stream;
}
/**
* Returns the connection status of Swarm node.
*
* @throws CurlException|StorageNotAvailableException
*/
private function checkConnection(): bool {
if ($this->isVersion()) {
return $this->checkConnectionV1();
}
$endpoint = $this->api_url.'/api/readiness';
$request = new Curl($endpoint, authorization: $this->access_key);
$request->get();
$statusCode = $request->getStatusCode();
if (!$request->isResponseSuccessful()) {
if (401 === $statusCode) {
throw new StorageNotAvailableException('Invalid access key');
}
throw new StorageNotAvailableException('Failed to connect to HejBit');
}
if (204 !== $statusCode) {
throw new StorageNotAvailableException('Failed to connect to HejBit');
}
return true;
}
/**
* @throws CurlException
*/
private function checkConnectionV1(): bool {
$endpoint = $this->api_url.DIRECTORY_SEPARATOR.'readiness';
$request = new Curl($endpoint);
$request->setAuthorization($this->access_key, CURLAUTH_ANY);
$output = $request->get();
$statusCode = $request->getStatusCode();
return 200 === $statusCode and 'OK' === $output;
}
/**
* @return resource
*
* @throws CurlException|HejBitException
*/
private function downloadSwarmV1(string $reference) {
$endpoint = $this->api_url.DIRECTORY_SEPARATOR.'bzz'.DIRECTORY_SEPARATOR.$reference.DIRECTORY_SEPARATOR;
$request = new Curl($endpoint, [
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_MAXREDIRS => 10,
CURLOPT_HEADER => false,
], [
'content-type: application/octet-stream',
]);
$request->setAuthorization($this->access_key, CURLAUTH_ANY);
$response = $request->get();
$httpCode = $request->getInfo(CURLINFO_HTTP_CODE);
if (200 !== $httpCode) {
throw new HejBitException('Failed to download file from HejBit');
}
$stream = fopen('php://memory', 'r+');
fwrite($stream, $response);
rewind($stream);
return $stream;
}
/**
* @throws CurlException|HejBitException
*/
private function uploadSwarmV1(string $path, string $tempFile, string $mimetype): array|string {
$endpoint = $this->api_url.DIRECTORY_SEPARATOR.'bzz';
$params = '?name='.urlencode(basename($path));
$request = new Curl($endpoint.$params, [
CURLOPT_PUT => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POST => true,
CURLOPT_INFILE => fopen($tempFile, 'r'),
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_VERBOSE => true,
], [
'content-type: '.$mimetype,
'swarm-pin: true',
'swarm-redundancy-level: 2',
]);
$request->setAuthorization($this->access_key, CURLAUTH_ANY);
$result = $request->exec(true);
$reference = ($result['reference'] ?? null);
if (!isset($reference)) {
throw new HejBitException('Failed to upload file to HejBit: '.$result['message']);
}
return $reference;
}
}