Completed
Pull Request — master (#77)
by Maxence
03:09
created

FederatedController::receiveFederatedDelivery()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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