Passed
Push — master ( 3f4fb9...9e2ced )
by Morris
13:23 queued 11s
created

S3ConnectionTrait::testTimeout()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 3
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 Morris Jobke <[email protected]>
9
 * @author Robin Appelman <[email protected]>
10
 * @author S. Cat <[email protected]>
11
 * @author Stephen Cuppett <[email protected]>
12
 *
13
 * @license GNU AGPL version 3 or any later version
14
 *
15
 * This program is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License as
17
 * published by the Free Software Foundation, either version 3 of the
18
 * License, or (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License
26
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
27
 *
28
 */
29
30
namespace OC\Files\ObjectStore;
31
32
use Aws\ClientResolver;
33
use Aws\Credentials\CredentialProvider;
34
use Aws\Credentials\Credentials;
35
use Aws\Exception\CredentialsException;
36
use Aws\S3\Exception\S3Exception;
37
use Aws\S3\S3Client;
38
use GuzzleHttp\Promise;
39
use GuzzleHttp\Promise\RejectedPromise;
40
use OCP\ILogger;
41
42
trait S3ConnectionTrait {
43
	/** @var array */
44
	protected $params;
45
46
	/** @var S3Client */
47
	protected $connection;
48
49
	/** @var string */
50
	protected $id;
51
52
	/** @var string */
53
	protected $bucket;
54
55
	/** @var int */
56
	protected $timeout;
57
58
	/** @var int */
59
	protected $uploadPartSize;
60
61
	protected $test;
62
63
	protected function parseParams($params) {
64
		if (empty($params['bucket'])) {
65
			throw new \Exception("Bucket has to be configured.");
66
		}
67
68
		$this->id = 'amazon::' . $params['bucket'];
69
70
		$this->test = isset($params['test']);
71
		$this->bucket = $params['bucket'];
72
		$this->timeout = !isset($params['timeout']) ? 15 : $params['timeout'];
73
		$this->uploadPartSize = !isset($params['uploadPartSize']) ? 524288000 : $params['uploadPartSize'];
74
		$params['region'] = empty($params['region']) ? 'eu-west-1' : $params['region'];
75
		$params['hostname'] = empty($params['hostname']) ? 's3.' . $params['region'] . '.amazonaws.com' : $params['hostname'];
76
		if (!isset($params['port']) || $params['port'] === '') {
77
			$params['port'] = (isset($params['use_ssl']) && $params['use_ssl'] === false) ? 80 : 443;
78
		}
79
		$params['verify_bucket_exists'] = empty($params['verify_bucket_exists']) ? true : $params['verify_bucket_exists'];
80
		$this->params = $params;
81
	}
82
83
	public function getBucket() {
84
		return $this->bucket;
85
	}
86
87
	/**
88
	 * Returns the connection
89
	 *
90
	 * @return S3Client connected client
91
	 * @throws \Exception if connection could not be made
92
	 */
93
	public function getConnection() {
94
		if (!is_null($this->connection)) {
95
			return $this->connection;
96
		}
97
98
		$scheme = (isset($this->params['use_ssl']) && $this->params['use_ssl'] === false) ? 'http' : 'https';
99
		$base_url = $scheme . '://' . $this->params['hostname'] . ':' . $this->params['port'] . '/';
100
101
		// Adding explicit credential provider to the beginning chain.
102
		// Including environment variables and IAM instance profiles.
103
		$provider = CredentialProvider::memoize(
104
			CredentialProvider::chain(
105
				$this->paramCredentialProvider(),
106
				CredentialProvider::env(),
107
				CredentialProvider::instanceProfile()
108
			)
109
		);
110
111
		$options = [
112
			'version' => isset($this->params['version']) ? $this->params['version'] : 'latest',
113
			'credentials' => $provider,
114
			'endpoint' => $base_url,
115
			'region' => $this->params['region'],
116
			'use_path_style_endpoint' => isset($this->params['use_path_style']) ? $this->params['use_path_style'] : false,
117
			'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider()),
118
			'csm' => false,
119
		];
120
		if (isset($this->params['proxy'])) {
121
			$options['request.options'] = ['proxy' => $this->params['proxy']];
122
		}
123
		if (isset($this->params['legacy_auth']) && $this->params['legacy_auth']) {
124
			$options['signature_version'] = 'v2';
125
		}
126
		$this->connection = new S3Client($options);
127
128
		if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
129
			$logger = \OC::$server->getLogger();
130
			$logger->debug('Bucket "' . $this->bucket . '" This bucket name is not dns compatible, it may contain invalid characters.',
131
					 ['app' => 'objectstore']);
132
		}
133
134
		if ($this->params['verify_bucket_exists'] && !$this->connection->doesBucketExist($this->bucket)) {
135
			$logger = \OC::$server->getLogger();
136
			try {
137
				$logger->info('Bucket "' . $this->bucket . '" does not exist - creating it.', ['app' => 'objectstore']);
138
				if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
139
					throw new \Exception("The bucket will not be created because the name is not dns compatible, please correct it: " . $this->bucket);
140
				}
141
				$this->connection->createBucket(['Bucket' => $this->bucket]);
142
				$this->testTimeout();
143
			} catch (S3Exception $e) {
144
				$logger->logException($e, [
145
					'message' => 'Invalid remote storage.',
146
					'level' => ILogger::DEBUG,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::DEBUG has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

146
					'level' => /** @scrutinizer ignore-deprecated */ ILogger::DEBUG,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
147
					'app' => 'objectstore',
148
				]);
149
				throw new \Exception('Creation of bucket "' . $this->bucket . '" failed. ' . $e->getMessage());
150
			}
151
		}
152
153
		// google cloud's s3 compatibility doesn't like the EncodingType parameter
154
		if (strpos($base_url, 'storage.googleapis.com')) {
155
			$this->connection->getHandlerList()->remove('s3.auto_encode');
156
		}
157
158
		return $this->connection;
159
	}
160
161
	/**
162
	 * when running the tests wait to let the buckets catch up
163
	 */
164
	private function testTimeout() {
165
		if ($this->test) {
166
			sleep($this->timeout);
167
		}
168
	}
169
170
	public static function legacySignatureProvider($version, $service, $region) {
0 ignored issues
show
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

170
	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...
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

170
	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...
171
		switch ($version) {
172
			case 'v2':
173
			case 's3':
174
				return new S3Signature();
175
			default:
176
				return null;
177
		}
178
	}
179
180
	/**
181
	 * This function creates a credential provider based on user parameter file
182
	 */
183
	protected function paramCredentialProvider() : callable {
184
		return function () {
185
			$key = empty($this->params['key']) ? null : $this->params['key'];
186
			$secret = empty($this->params['secret']) ? null : $this->params['secret'];
187
188
			if ($key && $secret) {
189
				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

189
				return /** @scrutinizer ignore-call */ Promise\promise_for(
Loading history...
190
					new Credentials($key, $secret)
191
				);
192
			}
193
194
			$msg = 'Could not find parameters set for credentials in config file.';
195
			return new RejectedPromise(new CredentialsException($msg));
196
		};
197
	}
198
}
199