Passed
Push — master ( 75e863...919a84 )
by Julius
15:48 queued 12s
created

S3ConnectionTrait::legacySignatureProvider()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 3
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 Robin Appelman <[email protected]>
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Florent <[email protected]>
8
 * @author James Letendre <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Roeland Jago Douma <[email protected]>
12
 * @author S. Cat <[email protected]>
13
 * @author Stephen Cuppett <[email protected]>
14
 * @author Jasper Weyne <[email protected]>
15
 *
16
 * @license GNU AGPL version 3 or any later version
17
 *
18
 * This program is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License as
20
 * published by the Free Software Foundation, either version 3 of the
21
 * License, or (at your option) any later version.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30
 *
31
 */
32
33
namespace OC\Files\ObjectStore;
34
35
use Aws\ClientResolver;
36
use Aws\Credentials\CredentialProvider;
37
use Aws\Credentials\Credentials;
38
use Aws\Exception\CredentialsException;
39
use Aws\S3\Exception\S3Exception;
40
use Aws\S3\S3Client;
41
use GuzzleHttp\Promise;
42
use GuzzleHttp\Promise\RejectedPromise;
43
use OCP\ICertificateManager;
44
use Psr\Log\LoggerInterface;
45
46
trait S3ConnectionTrait {
47
	/** @var array */
48
	protected $params;
49
50
	/** @var S3Client */
51
	protected $connection;
52
53
	/** @var string */
54
	protected $id;
55
56
	/** @var string */
57
	protected $bucket;
58
59
	/** @var int */
60
	protected $timeout;
61
62
	/** @var string */
63
	protected $proxy;
64
65
	/** @var string */
66
	protected $storageClass;
67
68
	/** @var int */
69
	protected $uploadPartSize;
70
71
	/** @var int */
72
	private $putSizeLimit;
73
74
	protected $test;
75
76
	protected function parseParams($params) {
77
		if (empty($params['bucket'])) {
78
			throw new \Exception("Bucket has to be configured.");
79
		}
80
81
		$this->id = 'amazon::' . $params['bucket'];
82
83
		$this->test = isset($params['test']);
84
		$this->bucket = $params['bucket'];
85
		$this->proxy = $params['proxy'] ?? false;
0 ignored issues
show
Documentation Bug introduced by
It seems like $params['proxy'] ?? false can also be of type false. However, the property $proxy is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
86
		$this->timeout = $params['timeout'] ?? 15;
87
		$this->storageClass = !empty($params['storageClass']) ? $params['storageClass'] : 'STANDARD';
88
		$this->uploadPartSize = $params['uploadPartSize'] ?? 524288000;
89
		$this->putSizeLimit = $params['putSizeLimit'] ?? 104857600;
90
		$params['region'] = empty($params['region']) ? 'eu-west-1' : $params['region'];
91
		$params['hostname'] = empty($params['hostname']) ? 's3.' . $params['region'] . '.amazonaws.com' : $params['hostname'];
92
		if (!isset($params['port']) || $params['port'] === '') {
93
			$params['port'] = (isset($params['use_ssl']) && $params['use_ssl'] === false) ? 80 : 443;
94
		}
95
		$params['verify_bucket_exists'] = empty($params['verify_bucket_exists']) ? true : $params['verify_bucket_exists'];
96
		$this->params = $params;
97
	}
98
99
	public function getBucket() {
100
		return $this->bucket;
101
	}
102
103
	public function getProxy() {
104
		return $this->proxy;
105
	}
106
107
	/**
108
	 * Returns the connection
109
	 *
110
	 * @return S3Client connected client
111
	 * @throws \Exception if connection could not be made
112
	 */
113
	public function getConnection() {
114
		if (!is_null($this->connection)) {
115
			return $this->connection;
116
		}
117
118
		$scheme = (isset($this->params['use_ssl']) && $this->params['use_ssl'] === false) ? 'http' : 'https';
119
		$base_url = $scheme . '://' . $this->params['hostname'] . ':' . $this->params['port'] . '/';
120
121
		// Adding explicit credential provider to the beginning chain.
122
		// Including default credential provider (skipping AWS shared config files).
123
		$provider = CredentialProvider::memoize(
124
			CredentialProvider::chain(
125
				$this->paramCredentialProvider(),
126
				CredentialProvider::defaultProvider(['use_aws_shared_config_files' => false])
127
			)
128
		);
129
130
		$options = [
131
			'version' => isset($this->params['version']) ? $this->params['version'] : 'latest',
132
			'credentials' => $provider,
133
			'endpoint' => $base_url,
134
			'region' => $this->params['region'],
135
			'use_path_style_endpoint' => isset($this->params['use_path_style']) ? $this->params['use_path_style'] : false,
136
			'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider()),
137
			'csm' => false,
138
			'use_arn_region' => false,
139
			'http' => ['verify' => $this->getCertificateBundlePath()],
140
			'use_aws_shared_config_files' => false,
141
		];
142
		if ($this->getProxy()) {
143
			$options['http']['proxy'] = $this->getProxy();
144
		}
145
		if (isset($this->params['legacy_auth']) && $this->params['legacy_auth']) {
146
			$options['signature_version'] = 'v2';
147
		}
148
		$this->connection = new S3Client($options);
149
150
		if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
151
			$logger = \OC::$server->get(LoggerInterface::class);
152
			$logger->debug('Bucket "' . $this->bucket . '" This bucket name is not dns compatible, it may contain invalid characters.',
153
				['app' => 'objectstore']);
154
		}
155
156
		if ($this->params['verify_bucket_exists'] && !$this->connection->doesBucketExist($this->bucket)) {
157
			$logger = \OC::$server->get(LoggerInterface::class);
158
			try {
159
				$logger->info('Bucket "' . $this->bucket . '" does not exist - creating it.', ['app' => 'objectstore']);
160
				if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
161
					throw new \Exception("The bucket will not be created because the name is not dns compatible, please correct it: " . $this->bucket);
162
				}
163
				$this->connection->createBucket(['Bucket' => $this->bucket]);
164
				$this->testTimeout();
165
			} catch (S3Exception $e) {
166
				$logger->debug('Invalid remote storage.', [
167
					'exception' => $e,
168
					'app' => 'objectstore',
169
				]);
170
				throw new \Exception('Creation of bucket "' . $this->bucket . '" failed. ' . $e->getMessage());
171
			}
172
		}
173
174
		// google cloud's s3 compatibility doesn't like the EncodingType parameter
175
		if (strpos($base_url, 'storage.googleapis.com')) {
176
			$this->connection->getHandlerList()->remove('s3.auto_encode');
177
		}
178
179
		return $this->connection;
180
	}
181
182
	/**
183
	 * when running the tests wait to let the buckets catch up
184
	 */
185
	private function testTimeout() {
186
		if ($this->test) {
187
			sleep($this->timeout);
188
		}
189
	}
190
191
	public static function legacySignatureProvider($version, $service, $region) {
0 ignored issues
show
Unused Code introduced by
The parameter $service is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

191
	public static function legacySignatureProvider($version, /** @scrutinizer ignore-unused */ $service, $region) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $region is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

191
	public static function legacySignatureProvider($version, $service, /** @scrutinizer ignore-unused */ $region) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
192
		switch ($version) {
193
			case 'v2':
194
			case 's3':
195
				return new S3Signature();
196
			default:
197
				return null;
198
		}
199
	}
200
201
	/**
202
	 * This function creates a credential provider based on user parameter file
203
	 */
204
	protected function paramCredentialProvider(): callable {
205
		return function () {
206
			$key = empty($this->params['key']) ? null : $this->params['key'];
207
			$secret = empty($this->params['secret']) ? null : $this->params['secret'];
208
209
			if ($key && $secret) {
210
				return Promise\promise_for(
0 ignored issues
show
Bug introduced by
The function promise_for was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

210
				return /** @scrutinizer ignore-call */ Promise\promise_for(
Loading history...
211
					new Credentials($key, $secret)
212
				);
213
			}
214
215
			$msg = 'Could not find parameters set for credentials in config file.';
216
			return new RejectedPromise(new CredentialsException($msg));
217
		};
218
	}
219
220
	protected function getCertificateBundlePath(): ?string {
221
		if ((int)($this->params['use_nextcloud_bundle'] ?? "0")) {
222
			// since we store the certificate bundles on the primary storage, we can't get the bundle while setting up the primary storage
223
			if (!isset($this->params['primary_storage'])) {
224
				/** @var ICertificateManager $certManager */
225
				$certManager = \OC::$server->get(ICertificateManager::class);
226
				return $certManager->getAbsoluteBundlePath();
227
			} else {
228
				return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt';
229
			}
230
		} else {
231
			return null;
232
		}
233
	}
234
235
	protected function getSSECKey(): ?string {
236
		if (isset($this->params['sse_c_key'])) {
237
			return $this->params['sse_c_key'];
238
		}
239
240
		return null;
241
	}
242
243
	protected function getSSECParameters(bool $copy = false): array {
244
		$key = $this->getSSECKey();
245
246
		if ($key === null) {
247
			return [];
248
		}
249
250
		$rawKey = base64_decode($key);
251
		if ($copy) {
252
			return [
253
				'CopySourceSSECustomerAlgorithm' => 'AES256',
254
				'CopySourceSSECustomerKey' => $rawKey,
255
				'CopySourceSSECustomerKeyMD5' => md5($rawKey, true)
256
			];
257
		}
258
		return [
259
			'SSECustomerAlgorithm' => 'AES256',
260
			'SSECustomerKey' => $rawKey,
261
			'SSECustomerKeyMD5' => md5($rawKey, true)
262
		];
263
	}
264
}
265