Completed
Push — federated-circles ( 33013b...a68948 )
by Maxence
04:32
created

FederatedController   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 211
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 20
c 1
b 0
f 0
lcom 1
cbo 8
dl 0
loc 211
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
C requestedLink() 0 33 7
B initFederatedDelivery() 0 23 5
A receiveFederatedDelivery() 0 16 4
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 OC\AppFramework\Http;
30
use OCA\Circles\Model\FederatedLink;
31
use OCA\Circles\Model\SharingFrame;
32
use OCA\Circles\Service\FederatedService;
33
use OCA\Circles\Service\CirclesService;
34
use OCA\Circles\Service\ConfigService;
35
use OCA\Circles\Service\MembersService;
36
use OCA\Circles\Service\MiscService;
37
use OCA\Circles\Service\SharesService;
38
use OCP\AppFramework\Http\DataResponse;
39
use OCP\IL10N;
40
41
class FederatedController extends BaseController {
42
43
	/** @var string */
44
	protected $userId;
45
46
	/** @var IL10N */
47
	protected $l10n;
48
49
	/** @var ConfigService */
50
	protected $configService;
51
52
	/** @var CirclesService */
53
	protected $circlesService;
54
55
	/** @var MembersService */
56
	protected $membersService;
57
58
	/** @var SharesService */
59
	protected $sharesService;
60
61
	/** @var FederatedService */
62
	protected $federatedService;
63
64
	/** @var MiscService */
65
	protected $miscService;
66
67
68
	/**
69
	 * requestedLink()
70
	 *
71
	 * Called when a remote circle want to create a link.
72
	 * The function check if it is possible first; then create a link- object
73
	 * and sent it to be saved in the database.
74
	 *
75
	 * @PublicPage
76
	 * @NoCSRFRequired
77
	 *
78
	 * @param string $token
79
	 * @param string $uniqueId
80
	 * @param string $sourceName
81
	 * @param string $linkTo
82
	 * @param string $address
83
	 *
84
	 * @return DataResponse
85
	 */
86
	public function requestedLink($token, $uniqueId, $sourceName, $linkTo, $address) {
87
88
		if ($uniqueId === '' || !$this->configService->isFederatedAllowed()) {
89
			return $this->federatedFail('federated_not_allowed');
90
		}
91
92
		$circle = $this->circlesService->infoCircleByName($linkTo);
93
		if ($circle === null) {
94
			return $this->federatedFail('circle_does_not_exist');
95
		}
96
97
		if ($circle->getUniqueId() === $uniqueId) {
98
			return $this->federatedFail('duplicate_unique_id');
99
		}
100
101
		if ($this->federatedService->getLink($circle->getId(), $uniqueId) !== null) {
102
			return $this->federatedFail('duplicate_link');
103
		}
104
105
		$link = new FederatedLink();
106
		$link->setToken($token)
107
			 ->setUniqueId($uniqueId)
108
			 ->setRemoteCircleName($sourceName)
109
			 ->setAddress($address);
110
111
		if ($this->federatedService->initiateLink($circle, $link)) {
112
			return $this->federatedSuccess(
113
				['status' => $link->getStatus(), 'uniqueId' => $circle->getUniqueId()], $link
114
			);
115
		} else {
116
			return $this->federatedFail('link_failed');
117
		}
118
	}
119
120
121
	/**
122
	 * initFederatedDelivery()
123
	 *
124
	 * Note: this function will close the request mid-run from the client but will still
125
	 * running its process.
126
	 * Called by locally, the function will get the SharingFrame by its uniqueId from the database,
127
	 * assign him some Headers and will deliver it to each remotes linked to the circle the Payload
128
	 * belongs to. A status response is sent to free the client process before starting to
129
	 * broadcast the item to other federated links.
130
	 *
131
	 * @PublicPage
132
	 * @NoCSRFRequired
133
	 *
134
	 * @param $circleId
135
	 * @param $uniqueId
136
	 *
137
	 * @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...
138
	 */
139
	public function initFederatedDelivery($circleId, $uniqueId) {
140
141
		if ($uniqueId === '' || !$this->configService->isFederatedAllowed()) {
142
			return $this->federatedFail('federated_not_allowed');
143
		}
144
145
		$frame = $this->sharesService->getFrameFromUniqueId($circleId, $uniqueId);
146
		if ($frame === null) {
147
			return $this->federatedFail('unknown_share');
148
		}
149
150
		if ($frame->getCloudId() !== null) {
151
			return $this->federatedFail('share_already_delivered');
152
		}
153
154
		// We don't want to keep the connection up
155
		$this->asyncAndLeaveClientOutOfThis('done');
156
157
		$this->federatedService->updateFrameWithCloudId($frame);
158
		$this->federatedService->sendRemoteShare($frame);
159
160
		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...
161
	}
162
163
164
	/**
165
	 * receiveFederatedDelivery()
166
	 *
167
	 * Note: this function will close the request mid-run from the client but will still
168
	 * running its process.
169
	 * Called by a remote circle to broadcast a Share item, the function will save the item
170
	 * in the database and broadcast it locally. A status response is sent to the remote to free
171
	 * the remote process before starting to broadcast the item to other federated links.
172
	 *
173
	 * @PublicPage
174
	 * @NoCSRFRequired
175
	 *
176
	 * @param $token
177
	 * @param $uniqueId
178
	 * @param $item
179
	 *
180
	 * @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...
181
	 */
182
	public function receiveFederatedDelivery($token, $uniqueId, $item) {
183
184
		if ($uniqueId === '' || !$this->configService->isFederatedAllowed()) {
185
			return $this->federatedFail('federated_not_allowed');
186
		}
187
188
		$frame = SharingFrame::fromJSON($item);
189
		if (!$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 188 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
			return $this->federatedFail('shares_is_already_known');
191
		}
192
193
		$this->asyncAndLeaveClientOutOfThis('done');
194
195
		$this->federatedService->sendRemoteShare($frame);
196
		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...
197
	}
198
199
	/**
200
	 * Hacky way to async the rest of the process without keeping client on hold.
201
	 *
202
	 * @param string $result
203
	 */
204
	private function asyncAndLeaveClientOutOfThis($result = '') {
205
		if (ob_get_contents() !== false) {
206
			ob_end_clean();
207
		}
208
209
		header('Connection: close');
210
		ignore_user_abort();
211
		ob_start();
212
		echo($result);
213
		$size = ob_get_length();
214
		header('Content-Length: ' . $size);
215
		ob_end_flush();
216
		flush();
217
	}
218
219
	/**
220
	 * send a positive response to a request with an array of data, and confirm
221
	 * the identity of the link with a token
222
	 *
223
	 * @param array $data
224
	 * @param FederatedLink $link
225
	 *
226
	 * @return DataResponse
227
	 */
228
	private function federatedSuccess($data, $link) {
229
		return new DataResponse(
230
			array_merge($data, ['token' => $link->getToken()]), Http::STATUS_OK
231
		);
232
233
	}
234
235
	/**
236
	 * send a negative response to a request, with a reason of the failure.
237
	 *
238
	 * @param string $reason
239
	 *
240
	 * @return DataResponse
241
	 */
242
	private function federatedFail($reason) {
243
		return new DataResponse(
244
			[
245
				'status' => FederatedLink::STATUS_ERROR,
246
				'reason' => $reason
247
			],
248
			Http::STATUS_OK
249
		);
250
	}
251
}