Completed
Push — master ( c50c5a...8a05a3 )
by Robin
31:32
created
lib/private/Files/ObjectStore/S3ConnectionTrait.php 1 patch
Indentation   +274 added lines, -274 removed lines patch added patch discarded remove patch
@@ -24,278 +24,278 @@
 block discarded – undo
24 24
 use Psr\Log\LoggerInterface;
25 25
 
26 26
 trait S3ConnectionTrait {
27
-	use S3ConfigTrait;
28
-
29
-	protected string $id;
30
-
31
-	protected bool $test;
32
-
33
-	protected ?S3Client $connection = null;
34
-	private ?ICache $existingBucketsCache = null;
35
-	private bool $usePresignedUrl = false;
36
-
37
-	protected function parseParams($params) {
38
-		if (empty($params['bucket'])) {
39
-			throw new \Exception('Bucket has to be configured.');
40
-		}
41
-
42
-		if (isset($params['perBucket'][$params['bucket']])) {
43
-			$params = array_merge($params, $params['perBucket'][$params['bucket']]);
44
-		}
45
-
46
-		$this->id = 'amazon::' . $params['bucket'];
47
-
48
-		$this->test = isset($params['test']);
49
-		$this->bucket = $params['bucket'];
50
-		// Default to 5 like the S3 SDK does
51
-		$this->concurrency = $params['concurrency'] ?? 5;
52
-		$this->proxy = $params['proxy'] ?? false;
53
-		$this->connectTimeout = $params['connect_timeout'] ?? 5;
54
-		$this->timeout = $params['timeout'] ?? 15;
55
-		$this->storageClass = !empty($params['storageClass']) ? $params['storageClass'] : 'STANDARD';
56
-		$this->uploadPartSize = $params['uploadPartSize'] ?? 524288000;
57
-		$this->putSizeLimit = $params['putSizeLimit'] ?? 104857600;
58
-		$this->copySizeLimit = $params['copySizeLimit'] ?? 5242880000;
59
-		$this->useMultipartCopy = (bool)($params['useMultipartCopy'] ?? true);
60
-		$this->retriesMaxAttempts = $params['retriesMaxAttempts'] ?? 5;
61
-		$params['region'] = empty($params['region']) ? 'eu-west-1' : $params['region'];
62
-		$params['hostname'] = empty($params['hostname']) ? 's3.' . $params['region'] . '.amazonaws.com' : $params['hostname'];
63
-		$params['s3-accelerate'] = $params['hostname'] === 's3-accelerate.amazonaws.com' || $params['hostname'] === 's3-accelerate.dualstack.amazonaws.com';
64
-		if (!isset($params['port']) || $params['port'] === '') {
65
-			$params['port'] = (isset($params['use_ssl']) && $params['use_ssl'] === false) ? 80 : 443;
66
-		}
67
-		$params['verify_bucket_exists'] = $params['verify_bucket_exists'] ?? true;
68
-
69
-		if ($params['s3-accelerate']) {
70
-			$params['verify_bucket_exists'] = false;
71
-		}
72
-
73
-		$this->params = $params;
74
-	}
75
-
76
-	public function getBucket() {
77
-		return $this->bucket;
78
-	}
79
-
80
-	public function getProxy() {
81
-		return $this->proxy;
82
-	}
83
-
84
-	/**
85
-	 * Returns the connection
86
-	 *
87
-	 * @return S3Client connected client
88
-	 * @throws \Exception if connection could not be made
89
-	 */
90
-	public function getConnection() {
91
-		if ($this->connection !== null) {
92
-			return $this->connection;
93
-		}
94
-
95
-		if ($this->existingBucketsCache === null) {
96
-			$this->existingBucketsCache = Server::get(ICacheFactory::class)
97
-				->createLocal('s3-bucket-exists-cache');
98
-		}
99
-
100
-		$scheme = (isset($this->params['use_ssl']) && $this->params['use_ssl'] === false) ? 'http' : 'https';
101
-		$base_url = $scheme . '://' . $this->params['hostname'] . ':' . $this->params['port'] . '/';
102
-
103
-		// Adding explicit credential provider to the beginning chain.
104
-		// Including default credential provider (skipping AWS shared config files).
105
-		$provider = CredentialProvider::memoize(
106
-			CredentialProvider::chain(
107
-				$this->paramCredentialProvider(),
108
-				CredentialProvider::defaultProvider(['use_aws_shared_config_files' => false])
109
-			)
110
-		);
111
-
112
-		$this->usePresignedUrl = $this->params['use_presigned_url'] ?? false;
113
-
114
-		$options = [
115
-			'version' => $this->params['version'] ?? 'latest',
116
-			'credentials' => $provider,
117
-			'endpoint' => $base_url,
118
-			'region' => $this->params['region'],
119
-			'use_path_style_endpoint' => isset($this->params['use_path_style']) ? $this->params['use_path_style'] : false,
120
-			'proxy' => isset($this->params['proxy']) ? $this->params['proxy'] : false,
121
-			'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider()),
122
-			'csm' => false,
123
-			'use_arn_region' => false,
124
-			'http' => [
125
-				'verify' => $this->getCertificateBundlePath(),
126
-				'connect_timeout' => $this->connectTimeout,
127
-			],
128
-			'use_aws_shared_config_files' => false,
129
-			'retries' => [
130
-				'mode' => 'standard',
131
-				'max_attempts' => $this->retriesMaxAttempts,
132
-			],
133
-		];
134
-
135
-		if ($this->params['s3-accelerate']) {
136
-			$options['use_accelerate_endpoint'] = true;
137
-		} else {
138
-			$options['endpoint'] = $base_url;
139
-		}
140
-
141
-		if (isset($this->params['request_checksum_calculation'])) {
142
-			$options['request_checksum_calculation'] = $this->params['request_checksum_calculation'];
143
-		} else {
144
-			$options['request_checksum_calculation'] = 'when_required';
145
-		}
146
-
147
-		if (isset($this->params['response_checksum_validation'])) {
148
-			$options['response_checksum_validation'] = $this->params['response_checksum_validation'];
149
-		} else {
150
-			$options['response_checksum_validation'] = 'when_required';
151
-		}
152
-
153
-		if ($this->getProxy()) {
154
-			$options['http']['proxy'] = $this->getProxy();
155
-		}
156
-		if (isset($this->params['legacy_auth']) && $this->params['legacy_auth']) {
157
-			$options['signature_version'] = 'v2';
158
-		}
159
-		$this->connection = new S3Client($options);
160
-
161
-		try {
162
-			$logger = Server::get(LoggerInterface::class);
163
-			if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
164
-				$logger->debug('Bucket "' . $this->bucket . '" This bucket name is not dns compatible, it may contain invalid characters.',
165
-					['app' => 'objectstore']);
166
-			}
167
-
168
-			if ($this->params['verify_bucket_exists']) {
169
-				$cacheKey = $this->params['hostname'] . $this->bucket;
170
-				$exist = $this->existingBucketsCache->get($cacheKey) === 1;
171
-
172
-				if (!$exist) {
173
-					if (!$this->connection->doesBucketExist($this->bucket)) {
174
-						try {
175
-							$logger->info('Bucket "' . $this->bucket . '" does not exist - creating it.', ['app' => 'objectstore']);
176
-							if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
177
-								throw new StorageNotAvailableException('The bucket will not be created because the name is not dns compatible, please correct it: ' . $this->bucket);
178
-							}
179
-							$this->connection->createBucket(['Bucket' => $this->bucket]);
180
-							Server::get(IEventDispatcher::class)
181
-								->dispatchTyped(new BucketCreatedEvent(
182
-									$this->bucket,
183
-									$options['endpoint'],
184
-									$options['region'],
185
-									$options['version']
186
-								));
187
-							$this->testTimeout();
188
-						} catch (S3Exception $e) {
189
-							$logger->debug('Invalid remote storage.', [
190
-								'exception' => $e,
191
-								'app' => 'objectstore',
192
-							]);
193
-							if ($e->getAwsErrorCode() !== 'BucketAlreadyOwnedByYou') {
194
-								throw new StorageNotAvailableException('Creation of bucket "' . $this->bucket . '" failed. ' . $e->getMessage());
195
-							}
196
-						}
197
-					}
198
-					$this->existingBucketsCache->set($cacheKey, 1);
199
-				}
200
-			}
201
-
202
-			// google cloud's s3 compatibility doesn't like the EncodingType parameter
203
-			if (strpos($base_url, 'storage.googleapis.com')) {
204
-				$this->connection->getHandlerList()->remove('s3.auto_encode');
205
-			}
206
-		} catch (S3Exception $e) {
207
-			throw new StorageNotAvailableException('S3 service is unable to handle request: ' . $e->getMessage());
208
-		}
209
-
210
-		return $this->connection;
211
-	}
212
-
213
-	/**
214
-	 * when running the tests wait to let the buckets catch up
215
-	 */
216
-	private function testTimeout() {
217
-		if ($this->test) {
218
-			sleep($this->timeout);
219
-		}
220
-	}
221
-
222
-	public static function legacySignatureProvider($version, $service, $region) {
223
-		switch ($version) {
224
-			case 'v2':
225
-			case 's3':
226
-				return new S3Signature();
227
-			default:
228
-				return null;
229
-		}
230
-	}
231
-
232
-	/**
233
-	 * This function creates a credential provider based on user parameter file
234
-	 */
235
-	protected function paramCredentialProvider(): callable {
236
-		return function () {
237
-			$key = empty($this->params['key']) ? null : $this->params['key'];
238
-			$secret = empty($this->params['secret']) ? null : $this->params['secret'];
239
-			$sessionToken = empty($this->params['session_token']) ? null : $this->params['session_token'];
240
-
241
-			if ($key && $secret) {
242
-				return Create::promiseFor(
243
-					// a null sessionToken match the default signature of the constructor
244
-					new Credentials($key, $secret, $sessionToken)
245
-				);
246
-			}
247
-
248
-			$msg = 'Could not find parameters set for credentials in config file.';
249
-			return new RejectedPromise(new CredentialsException($msg));
250
-		};
251
-	}
252
-
253
-	protected function getCertificateBundlePath(): ?string {
254
-		if ((int)($this->params['use_nextcloud_bundle'] ?? '0')) {
255
-			/** @var ICertificateManager $certManager */
256
-			$certManager = Server::get(ICertificateManager::class);
257
-			// since we store the certificate bundles on the primary storage, we can't get the bundle while setting up the primary storage
258
-			if (!isset($this->params['primary_storage'])) {
259
-				return $certManager->getAbsoluteBundlePath();
260
-			} else {
261
-				return $certManager->getDefaultCertificatesBundlePath();
262
-			}
263
-		} else {
264
-			return null;
265
-		}
266
-	}
267
-
268
-	protected function getSSECKey(): ?string {
269
-		if (isset($this->params['sse_c_key']) && !empty($this->params['sse_c_key'])) {
270
-			return $this->params['sse_c_key'];
271
-		}
272
-
273
-		return null;
274
-	}
275
-
276
-	protected function getSSECParameters(bool $copy = false): array {
277
-		$key = $this->getSSECKey();
278
-
279
-		if ($key === null) {
280
-			return [];
281
-		}
282
-
283
-		$rawKey = base64_decode($key);
284
-		if ($copy) {
285
-			return [
286
-				'CopySourceSSECustomerAlgorithm' => 'AES256',
287
-				'CopySourceSSECustomerKey' => $rawKey,
288
-				'CopySourceSSECustomerKeyMD5' => md5($rawKey, true)
289
-			];
290
-		}
291
-		return [
292
-			'SSECustomerAlgorithm' => 'AES256',
293
-			'SSECustomerKey' => $rawKey,
294
-			'SSECustomerKeyMD5' => md5($rawKey, true)
295
-		];
296
-	}
297
-
298
-	public function isUsePresignedUrl(): bool {
299
-		return $this->usePresignedUrl;
300
-	}
27
+    use S3ConfigTrait;
28
+
29
+    protected string $id;
30
+
31
+    protected bool $test;
32
+
33
+    protected ?S3Client $connection = null;
34
+    private ?ICache $existingBucketsCache = null;
35
+    private bool $usePresignedUrl = false;
36
+
37
+    protected function parseParams($params) {
38
+        if (empty($params['bucket'])) {
39
+            throw new \Exception('Bucket has to be configured.');
40
+        }
41
+
42
+        if (isset($params['perBucket'][$params['bucket']])) {
43
+            $params = array_merge($params, $params['perBucket'][$params['bucket']]);
44
+        }
45
+
46
+        $this->id = 'amazon::' . $params['bucket'];
47
+
48
+        $this->test = isset($params['test']);
49
+        $this->bucket = $params['bucket'];
50
+        // Default to 5 like the S3 SDK does
51
+        $this->concurrency = $params['concurrency'] ?? 5;
52
+        $this->proxy = $params['proxy'] ?? false;
53
+        $this->connectTimeout = $params['connect_timeout'] ?? 5;
54
+        $this->timeout = $params['timeout'] ?? 15;
55
+        $this->storageClass = !empty($params['storageClass']) ? $params['storageClass'] : 'STANDARD';
56
+        $this->uploadPartSize = $params['uploadPartSize'] ?? 524288000;
57
+        $this->putSizeLimit = $params['putSizeLimit'] ?? 104857600;
58
+        $this->copySizeLimit = $params['copySizeLimit'] ?? 5242880000;
59
+        $this->useMultipartCopy = (bool)($params['useMultipartCopy'] ?? true);
60
+        $this->retriesMaxAttempts = $params['retriesMaxAttempts'] ?? 5;
61
+        $params['region'] = empty($params['region']) ? 'eu-west-1' : $params['region'];
62
+        $params['hostname'] = empty($params['hostname']) ? 's3.' . $params['region'] . '.amazonaws.com' : $params['hostname'];
63
+        $params['s3-accelerate'] = $params['hostname'] === 's3-accelerate.amazonaws.com' || $params['hostname'] === 's3-accelerate.dualstack.amazonaws.com';
64
+        if (!isset($params['port']) || $params['port'] === '') {
65
+            $params['port'] = (isset($params['use_ssl']) && $params['use_ssl'] === false) ? 80 : 443;
66
+        }
67
+        $params['verify_bucket_exists'] = $params['verify_bucket_exists'] ?? true;
68
+
69
+        if ($params['s3-accelerate']) {
70
+            $params['verify_bucket_exists'] = false;
71
+        }
72
+
73
+        $this->params = $params;
74
+    }
75
+
76
+    public function getBucket() {
77
+        return $this->bucket;
78
+    }
79
+
80
+    public function getProxy() {
81
+        return $this->proxy;
82
+    }
83
+
84
+    /**
85
+     * Returns the connection
86
+     *
87
+     * @return S3Client connected client
88
+     * @throws \Exception if connection could not be made
89
+     */
90
+    public function getConnection() {
91
+        if ($this->connection !== null) {
92
+            return $this->connection;
93
+        }
94
+
95
+        if ($this->existingBucketsCache === null) {
96
+            $this->existingBucketsCache = Server::get(ICacheFactory::class)
97
+                ->createLocal('s3-bucket-exists-cache');
98
+        }
99
+
100
+        $scheme = (isset($this->params['use_ssl']) && $this->params['use_ssl'] === false) ? 'http' : 'https';
101
+        $base_url = $scheme . '://' . $this->params['hostname'] . ':' . $this->params['port'] . '/';
102
+
103
+        // Adding explicit credential provider to the beginning chain.
104
+        // Including default credential provider (skipping AWS shared config files).
105
+        $provider = CredentialProvider::memoize(
106
+            CredentialProvider::chain(
107
+                $this->paramCredentialProvider(),
108
+                CredentialProvider::defaultProvider(['use_aws_shared_config_files' => false])
109
+            )
110
+        );
111
+
112
+        $this->usePresignedUrl = $this->params['use_presigned_url'] ?? false;
113
+
114
+        $options = [
115
+            'version' => $this->params['version'] ?? 'latest',
116
+            'credentials' => $provider,
117
+            'endpoint' => $base_url,
118
+            'region' => $this->params['region'],
119
+            'use_path_style_endpoint' => isset($this->params['use_path_style']) ? $this->params['use_path_style'] : false,
120
+            'proxy' => isset($this->params['proxy']) ? $this->params['proxy'] : false,
121
+            'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider()),
122
+            'csm' => false,
123
+            'use_arn_region' => false,
124
+            'http' => [
125
+                'verify' => $this->getCertificateBundlePath(),
126
+                'connect_timeout' => $this->connectTimeout,
127
+            ],
128
+            'use_aws_shared_config_files' => false,
129
+            'retries' => [
130
+                'mode' => 'standard',
131
+                'max_attempts' => $this->retriesMaxAttempts,
132
+            ],
133
+        ];
134
+
135
+        if ($this->params['s3-accelerate']) {
136
+            $options['use_accelerate_endpoint'] = true;
137
+        } else {
138
+            $options['endpoint'] = $base_url;
139
+        }
140
+
141
+        if (isset($this->params['request_checksum_calculation'])) {
142
+            $options['request_checksum_calculation'] = $this->params['request_checksum_calculation'];
143
+        } else {
144
+            $options['request_checksum_calculation'] = 'when_required';
145
+        }
146
+
147
+        if (isset($this->params['response_checksum_validation'])) {
148
+            $options['response_checksum_validation'] = $this->params['response_checksum_validation'];
149
+        } else {
150
+            $options['response_checksum_validation'] = 'when_required';
151
+        }
152
+
153
+        if ($this->getProxy()) {
154
+            $options['http']['proxy'] = $this->getProxy();
155
+        }
156
+        if (isset($this->params['legacy_auth']) && $this->params['legacy_auth']) {
157
+            $options['signature_version'] = 'v2';
158
+        }
159
+        $this->connection = new S3Client($options);
160
+
161
+        try {
162
+            $logger = Server::get(LoggerInterface::class);
163
+            if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
164
+                $logger->debug('Bucket "' . $this->bucket . '" This bucket name is not dns compatible, it may contain invalid characters.',
165
+                    ['app' => 'objectstore']);
166
+            }
167
+
168
+            if ($this->params['verify_bucket_exists']) {
169
+                $cacheKey = $this->params['hostname'] . $this->bucket;
170
+                $exist = $this->existingBucketsCache->get($cacheKey) === 1;
171
+
172
+                if (!$exist) {
173
+                    if (!$this->connection->doesBucketExist($this->bucket)) {
174
+                        try {
175
+                            $logger->info('Bucket "' . $this->bucket . '" does not exist - creating it.', ['app' => 'objectstore']);
176
+                            if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
177
+                                throw new StorageNotAvailableException('The bucket will not be created because the name is not dns compatible, please correct it: ' . $this->bucket);
178
+                            }
179
+                            $this->connection->createBucket(['Bucket' => $this->bucket]);
180
+                            Server::get(IEventDispatcher::class)
181
+                                ->dispatchTyped(new BucketCreatedEvent(
182
+                                    $this->bucket,
183
+                                    $options['endpoint'],
184
+                                    $options['region'],
185
+                                    $options['version']
186
+                                ));
187
+                            $this->testTimeout();
188
+                        } catch (S3Exception $e) {
189
+                            $logger->debug('Invalid remote storage.', [
190
+                                'exception' => $e,
191
+                                'app' => 'objectstore',
192
+                            ]);
193
+                            if ($e->getAwsErrorCode() !== 'BucketAlreadyOwnedByYou') {
194
+                                throw new StorageNotAvailableException('Creation of bucket "' . $this->bucket . '" failed. ' . $e->getMessage());
195
+                            }
196
+                        }
197
+                    }
198
+                    $this->existingBucketsCache->set($cacheKey, 1);
199
+                }
200
+            }
201
+
202
+            // google cloud's s3 compatibility doesn't like the EncodingType parameter
203
+            if (strpos($base_url, 'storage.googleapis.com')) {
204
+                $this->connection->getHandlerList()->remove('s3.auto_encode');
205
+            }
206
+        } catch (S3Exception $e) {
207
+            throw new StorageNotAvailableException('S3 service is unable to handle request: ' . $e->getMessage());
208
+        }
209
+
210
+        return $this->connection;
211
+    }
212
+
213
+    /**
214
+     * when running the tests wait to let the buckets catch up
215
+     */
216
+    private function testTimeout() {
217
+        if ($this->test) {
218
+            sleep($this->timeout);
219
+        }
220
+    }
221
+
222
+    public static function legacySignatureProvider($version, $service, $region) {
223
+        switch ($version) {
224
+            case 'v2':
225
+            case 's3':
226
+                return new S3Signature();
227
+            default:
228
+                return null;
229
+        }
230
+    }
231
+
232
+    /**
233
+     * This function creates a credential provider based on user parameter file
234
+     */
235
+    protected function paramCredentialProvider(): callable {
236
+        return function () {
237
+            $key = empty($this->params['key']) ? null : $this->params['key'];
238
+            $secret = empty($this->params['secret']) ? null : $this->params['secret'];
239
+            $sessionToken = empty($this->params['session_token']) ? null : $this->params['session_token'];
240
+
241
+            if ($key && $secret) {
242
+                return Create::promiseFor(
243
+                    // a null sessionToken match the default signature of the constructor
244
+                    new Credentials($key, $secret, $sessionToken)
245
+                );
246
+            }
247
+
248
+            $msg = 'Could not find parameters set for credentials in config file.';
249
+            return new RejectedPromise(new CredentialsException($msg));
250
+        };
251
+    }
252
+
253
+    protected function getCertificateBundlePath(): ?string {
254
+        if ((int)($this->params['use_nextcloud_bundle'] ?? '0')) {
255
+            /** @var ICertificateManager $certManager */
256
+            $certManager = Server::get(ICertificateManager::class);
257
+            // since we store the certificate bundles on the primary storage, we can't get the bundle while setting up the primary storage
258
+            if (!isset($this->params['primary_storage'])) {
259
+                return $certManager->getAbsoluteBundlePath();
260
+            } else {
261
+                return $certManager->getDefaultCertificatesBundlePath();
262
+            }
263
+        } else {
264
+            return null;
265
+        }
266
+    }
267
+
268
+    protected function getSSECKey(): ?string {
269
+        if (isset($this->params['sse_c_key']) && !empty($this->params['sse_c_key'])) {
270
+            return $this->params['sse_c_key'];
271
+        }
272
+
273
+        return null;
274
+    }
275
+
276
+    protected function getSSECParameters(bool $copy = false): array {
277
+        $key = $this->getSSECKey();
278
+
279
+        if ($key === null) {
280
+            return [];
281
+        }
282
+
283
+        $rawKey = base64_decode($key);
284
+        if ($copy) {
285
+            return [
286
+                'CopySourceSSECustomerAlgorithm' => 'AES256',
287
+                'CopySourceSSECustomerKey' => $rawKey,
288
+                'CopySourceSSECustomerKeyMD5' => md5($rawKey, true)
289
+            ];
290
+        }
291
+        return [
292
+            'SSECustomerAlgorithm' => 'AES256',
293
+            'SSECustomerKey' => $rawKey,
294
+            'SSECustomerKeyMD5' => md5($rawKey, true)
295
+        ];
296
+    }
297
+
298
+    public function isUsePresignedUrl(): bool {
299
+        return $this->usePresignedUrl;
300
+    }
301 301
 }
Please login to merge, or discard this patch.