Completed
Push — master ( 53fcb4...50b7c9 )
by Maxence
02:56
created

FederatedController   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 22
c 1
b 0
f 0
lcom 1
cbo 9
dl 0
loc 244
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
B requestedLink() 0 27 5
B initFederatedDelivery() 0 23 5
B receiveFederatedDelivery() 0 22 5
A updateLink() 0 12 3
A asyncAndLeaveClientOutOfThis() 0 14 2
A federatedSuccess() 0 6 1
A federatedFail() 0 9 1
1
<?php
2
/**
3
 * Circles - Bring cloud-users closer together.
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Maxence Lange <[email protected]>
9
 * @copyright 2017
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
27
namespace OCA\Circles\Controller;
28
29
use Exception;
30
use OC\AppFramework\Http;
31
use OCA\Circles\Exceptions\LinkCreationException;
32
use OCA\Circles\Model\FederatedLink;
33
use OCA\Circles\Model\SharingFrame;
34
use OCA\Circles\Service\FederatedService;
35
use OCA\Circles\Service\CirclesService;
36
use OCA\Circles\Service\ConfigService;
37
use OCA\Circles\Service\MembersService;
38
use OCA\Circles\Service\MiscService;
39
use OCA\Circles\Service\SharesService;
40
use OCP\AppFramework\Http\DataResponse;
41
use OCP\IL10N;
42
43
class FederatedController extends BaseController {
44
45
	/** @var string */
46
	protected $userId;
47
48
	/** @var IL10N */
49
	protected $l10n;
50
51
	/** @var ConfigService */
52
	protected $configService;
53
54
	/** @var CirclesService */
55
	protected $circlesService;
56
57
	/** @var MembersService */
58
	protected $membersService;
59
60
	/** @var SharesService */
61
	protected $sharesService;
62
63
	/** @var FederatedService */
64
	protected $federatedService;
65
66
	/** @var MiscService */
67
	protected $miscService;
68
69
70
	/**
71
	 * requestedLink()
72
	 *
73
	 * Called when a remote circle want to create a link.
74
	 * The function check if it is possible first; then create a link-object
75
	 * and sent it to be saved in the database.
76
	 *
77
	 * @PublicPage
78
	 * @NoCSRFRequired
79
	 *
80
	 * @param array $apiVersion
81
	 * @param string $token
82
	 * @param string $uniqueId
83
	 * @param string $sourceName
84
	 * @param string $linkTo
85
	 * @param string $address
86
	 *
87
	 * @return DataResponse
88
	 */
89
	public function requestedLink($apiVersion, $token, $uniqueId, $sourceName, $linkTo, $address) {
90
91
		if ($uniqueId === '' || !$this->configService->isFederatedAllowed()) {
92
			return $this->federatedFail('federated_not_allowed');
93
		}
94
95
		try {
96
			$circle = $this->circlesService->infoCircleByName($linkTo);
97
			if ($circle === null) {
98
				throw new LinkCreationException('circle_does_not_exist');
99
			}
100
101
			$link = new FederatedLink();
102
			$link->setToken($token)
103
				 ->setUniqueId($uniqueId)
104
				 ->setRemoteCircleName($sourceName)
105
				 ->setAddress($address);
106
107
			$this->federatedService->initiateLink($circle, $link);
108
109
			return $this->federatedSuccess(
110
				['status' => $link->getStatus(), 'uniqueId' => $circle->getUniqueId()], $link
111
			);
112
		} catch (Exception $e) {
113
			return $this->federatedFail($e->getMessage());
114
		}
115
	}
116
117
118
	/**
119
	 * initFederatedDelivery()
120
	 *
121
	 * Note: this function will close the request mid-run from the client but will still
122
	 * running its process.
123
	 * Called by locally, the function will get the SharingFrame by its uniqueId from the database,
124
	 * assign him some Headers and will deliver it to each remotes linked to the circle the Payload
125
	 * belongs to. A status response is sent to free the client process before starting to
126
	 * broadcast the item to other federated links.
127
	 *
128
	 * @PublicPage
129
	 * @NoCSRFRequired
130
	 *
131
	 * @param $apiVersion
132
	 * @param $circleId
133
	 * @param $uniqueId
134
	 *
135
	 * @return DataResponse
0 ignored issues
show
Documentation introduced by
Should the return type not be DataResponse|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
136
	 */
137
	public function initFederatedDelivery($apiVersion, $circleId, $uniqueId) {
138
139
		if ($uniqueId === '' || !$this->configService->isFederatedAllowed()) {
140
			return $this->federatedFail('federated_not_allowed');
141
		}
142
143
		$frame = $this->sharesService->getFrameFromUniqueId($circleId, $uniqueId);
144
		if ($frame === null) {
145
			return $this->federatedFail('unknown_share');
146
		}
147
148
		if ($frame->getCloudId() !== null) {
149
			return $this->federatedFail('share_already_delivered');
150
		}
151
152
		// We don't want to keep the connection up
153
		$this->asyncAndLeaveClientOutOfThis('done');
154
155
		$this->federatedService->updateFrameWithCloudId($frame);
156
		$this->federatedService->sendRemoteShare($frame);
157
158
		exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method initFederatedDelivery() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
159
	}
160
161
162
	/**
163
	 * receiveFederatedDelivery()
164
	 *
165
	 * Note: this function will close the request mid-run from the client but will still
166
	 * running its process.
167
	 * Called by a remote circle to broadcast a Share item, the function will save the item
168
	 * in the database and broadcast it locally. A status response is sent to the remote to free
169
	 * the remote process before starting to broadcast the item to other federated links.
170
	 *
171
	 * @PublicPage
172
	 * @NoCSRFRequired
173
	 *
174
	 * @param $apiVersion
175
	 * @param $token
176
	 * @param $uniqueId
177
	 * @param $item
178
	 *
179
	 * @return DataResponse
0 ignored issues
show
Documentation introduced by
Should the return type not be DataResponse|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
180
	 */
181
	public function receiveFederatedDelivery($apiVersion, $token, $uniqueId, $item) {
182
183
		if ($uniqueId === '' || !$this->configService->isFederatedAllowed()) {
184
			return $this->federatedFail('federated_not_allowed');
185
		}
186
187
		$frame = SharingFrame::fromJSON($item);
188
		try {
189
			$this->federatedService->receiveFrame($token, $uniqueId, $frame);
0 ignored issues
show
Bug introduced by
It seems like $frame defined by \OCA\Circles\Model\SharingFrame::fromJSON($item) on line 187 can be null; however, OCA\Circles\Service\Fede...Service::receiveFrame() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
190
		} catch (Exception $e) {
191
			return $this->federatedFail($e->getMessage());
192
		}
193
194
		$this->asyncAndLeaveClientOutOfThis('done');
195
196
		try {
197
			$this->federatedService->sendRemoteShare($frame);
198
		} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
199
		}
200
201
		exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method receiveFederatedDelivery() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
202
	}
203
204
205
	/**
206
	 * updateLink();
207
	 *
208
	 * Update the current status of a link, based on UniqueId and Token.
209
	 *
210
	 * @PublicPage
211
	 * @NoCSRFRequired
212
	 *
213
	 * @param $apiVersion
214
	 * @param $token
215
	 * @param $uniqueId
216
	 * @param $status
217
	 *
218
	 * @return DataResponse
219
	 */
220
	public function updateLink($apiVersion, $token, $uniqueId, $status) {
221
222
		if ($uniqueId === '' || !$this->configService->isFederatedAllowed()) {
223
			return $this->federatedFail('federated_not_allowed');
224
		}
225
226
		$link = $this->federatedService->updateLinkLocal($token, $uniqueId, $status);
227
228
		return $this->federatedSuccess(
229
			['status' => 1, 'link' => $link], $link
230
		);
231
	}
232
233
234
	/**
235
	 * Hacky way to async the rest of the process without keeping client on hold.
236
	 *
237
	 * @param string $result
238
	 */
239
	private function asyncAndLeaveClientOutOfThis($result = '') {
240
		if (ob_get_contents() !== false) {
241
			ob_end_clean();
242
		}
243
244
		header('Connection: close');
245
		ignore_user_abort();
246
		ob_start();
247
		echo($result);
248
		$size = ob_get_length();
249
		header('Content-Length: ' . $size);
250
		ob_end_flush();
251
		flush();
252
	}
253
254
	/**
255
	 * send a positive response to a request with an array of data, and confirm
256
	 * the identity of the link with a token
257
	 *
258
	 * @param array $data
259
	 * @param FederatedLink $link
260
	 *
261
	 * @return DataResponse
262
	 */
263
	private function federatedSuccess($data, $link) {
264
		return new DataResponse(
265
			array_merge($data, ['token' => $link->getToken()]), Http::STATUS_OK
266
		);
267
268
	}
269
270
	/**
271
	 * send a negative response to a request, with a reason of the failure.
272
	 *
273
	 * @param string $reason
274
	 *
275
	 * @return DataResponse
276
	 */
277
	private function federatedFail($reason) {
278
		return new DataResponse(
279
			[
280
				'status' => FederatedLink::STATUS_ERROR,
281
				'reason' => $reason
282
			],
283
			Http::STATUS_OK
284
		);
285
	}
286
}