Completed
Pull Request — master (#105)
by Maxence
02:19
created

FederatedController::updateLink()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 17
rs 9.2
cc 4
eloc 10
nc 4
nop 4
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\Api\v1\Circles;
32
use OCA\Circles\Exceptions\CircleDoesNotExistException;
33
use OCA\Circles\Exceptions\FederatedLinkCreationException;
34
use OCA\Circles\Exceptions\SharingFrameAlreadyExistException;
35
use OCA\Circles\Model\FederatedLink;
36
use OCA\Circles\Model\SharingFrame;
37
use OCA\Circles\Service\CirclesService;
38
use OCA\Circles\Service\ConfigService;
39
use OCA\Circles\Service\FederatedService;
40
use OCA\Circles\Service\MembersService;
41
use OCA\Circles\Service\MiscService;
42
use OCA\Circles\Service\SharesService;
43
use OCP\AppFramework\Http\DataResponse;
44
use OCP\IL10N;
45
46
class FederatedController extends BaseController {
47
48
	/** @var string */
49
	protected $userId;
50
51
	/** @var IL10N */
52
	protected $l10n;
53
54
	/** @var ConfigService */
55
	protected $configService;
56
57
	/** @var CirclesService */
58
	protected $circlesService;
59
60
	/** @var MembersService */
61
	protected $membersService;
62
63
	/** @var SharesService */
64
	protected $sharesService;
65
66
	/** @var FederatedService */
67
	protected $federatedService;
68
69
	/** @var MiscService */
70
	protected $miscService;
71
72
73
	/**
74
	 * requestedLink()
75
	 *
76
	 * Called when a remote circle want to create a link.
77
	 * The function check if it is possible first; then create a link-object
78
	 * and sent it to be saved in the database.
79
	 *
80
	 * @PublicPage
81
	 * @NoCSRFRequired
82
	 *
83
	 * @param array $apiVersion
84
	 * @param string $token
85
	 * @param string $uniqueId
86
	 * @param string $sourceName
87
	 * @param string $linkTo
88
	 * @param string $address
89
	 *
90
	 * @return DataResponse
91
	 * @throws FederatedLinkCreationException
92
	 */
93
	public function requestedLink($apiVersion, $token, $uniqueId, $sourceName, $linkTo, $address) {
94
95
		if ($uniqueId === '' || !$this->configService->isFederatedCirclesAllowed()) {
96
			return $this->federatedFail('federated_not_allowed');
97
		}
98
99
		try {
100
			Circles::compareVersion($apiVersion);
101
			$circle = $this->circlesService->infoCircleByName($linkTo);
102
			$link = new FederatedLink();
103
			$link->setToken($token)
104
				 ->setUniqueId($uniqueId)
105
				 ->setRemoteCircleName($sourceName)
106
				 ->setAddress($address);
107
108
			$this->federatedService->initiateLink($circle, $link);
0 ignored issues
show
Bug introduced by
It seems like $circle defined by $this->circlesService->infoCircleByName($linkTo) on line 101 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...
109
110
			return $this->federatedSuccess(
111
				['status' => $link->getStatus(), 'uniqueId' => $circle->getUniqueId(true)], $link
112
			);
113
		} catch (CircleDoesNotExistException $e) {
114
			return $this->federatedFail('circle_does_not_exist');
115
		} catch (Exception $e) {
116
			return $this->federatedFail($e->getMessage());
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 array $apiVersion
135
	 * @param string $circleId
136
	 * @param string $frameId
137
	 *
138
	 * @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...
139
	 */
140 View Code Duplication
	public function initFederatedDelivery($apiVersion, $circleId, $frameId) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
141
142
		if ($frameId === '' || !$this->configService->isFederatedCirclesAllowed()) {
143
			return $this->federatedFail('federated_not_allowed');
144
		}
145
146
		try {
147
			Circles::compareVersion($apiVersion);
148
			$frame = $this->sharesService->getFrameFromUniqueId($circleId, $frameId);
149
		} catch (Exception $e) {
150
			return $this->federatedFail($e->getMessage());
151
		}
152
153
		// We don't want to keep the connection up
154
		$this->asyncAndLeaveClientOutOfThis('done');
155
156
		$this->federatedService->updateFrameWithCloudId($frame);
0 ignored issues
show
Bug introduced by
It seems like $frame defined by $this->sharesService->ge...Id($circleId, $frameId) on line 148 can be null; however, OCA\Circles\Service\Fede...pdateFrameWithCloudId() 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...
157
		$this->federatedService->sendRemoteShare($frame);
0 ignored issues
show
Bug introduced by
It seems like $frame defined by $this->sharesService->ge...Id($circleId, $frameId) on line 148 can be null; however, OCA\Circles\Service\Fede...vice::sendRemoteShare() 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...
158
159
		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...
160
	}
161
162
163
	/**
164
	 * receiveFederatedDelivery()
165
	 *
166
	 * Note: this function will close the request mid-run from the client but will still
167
	 * running its process.
168
	 * Called by a remote circle to broadcast a Share item, the function will save the item
169
	 * in the database and broadcast it locally. A status response is sent to the remote to free
170
	 * the remote process before starting to broadcast the item to other federated links.
171
	 *
172
	 * @PublicPage
173
	 * @NoCSRFRequired
174
	 *
175
	 * @param array $apiVersion
176
	 * @param string $token
177
	 * @param string $uniqueId
178
	 * @param string $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 View Code Duplication
	public function receiveFederatedDelivery($apiVersion, $token, $uniqueId, $item) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
183
184
		if ($uniqueId === '' || !$this->configService->isFederatedCirclesAllowed()) {
185
			return $this->federatedFail('federated_not_allowed');
186
		}
187
188
		try {
189
			Circles::compareVersion($apiVersion);
190
			$frame = SharingFrame::fromJSON($item);
191
			$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 190 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...
192
		} catch (SharingFrameAlreadyExistException $e) {
193
			return $this->federatedSuccess();
194
		} catch (Exception $e) {
195
			return $this->federatedFail($e->getMessage());
196
		}
197
198
		$this->asyncAndLeaveClientOutOfThis('done');
199
		$this->federatedService->sendRemoteShare($frame);
200
		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...
201
	}
202
203
204
	/**
205
	 * updateLink();
206
	 *
207
	 * Update the current status of a link, based on UniqueId and Token.
208
	 *
209
	 * @PublicPage
210
	 * @NoCSRFRequired
211
	 *
212
	 * @param array $apiVersion
213
	 * @param string $token
214
	 * @param string $uniqueId
215
	 * @param $status
216
	 *
217
	 * @return DataResponse
218
	 */
219
	public function updateLink($apiVersion, $token, $uniqueId, $status) {
220
221
		if ($uniqueId === '' || !$this->configService->isFederatedCirclesAllowed()) {
222
			return $this->federatedFail('federated_not_allowed');
223
		}
224
225
		try {
226
			Circles::compareVersion($apiVersion);
227
			$link = $this->federatedService->updateLinkFromRemote($token, $uniqueId, $status);
228
		} catch (Exception $e) {
229
			return $this->federatedFail($e->getMessage());
230
		}
231
232
		return $this->federatedSuccess(
233
			['status' => 1, 'link' => $link], $link
234
		);
235
	}
236
237
238
	/**
239
	 * Hacky way to async the rest of the process without keeping client on hold.
240
	 *
241
	 * @param string $result
242
	 */
243
	private function asyncAndLeaveClientOutOfThis($result = '') {
244
		if (ob_get_contents() !== false) {
245
			ob_end_clean();
246
		}
247
248
		header('Connection: close');
249
		ignore_user_abort();
250
		ob_start();
251
		echo($result);
252
		$size = ob_get_length();
253
		header('Content-Length: ' . $size);
254
		ob_end_flush();
255
		flush();
256
	}
257
258
	/**
259
	 * send a positive response to a request with an array of data, and confirm
260
	 * the identity of the link with a token
261
	 *
262
	 * @param array $data
263
	 * @param FederatedLink $link
0 ignored issues
show
Documentation introduced by
Should the type for parameter $link not be null|FederatedLink?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
264
	 *
265
	 * @return DataResponse
266
	 */
267
	private function federatedSuccess(array $data = array(), FederatedLink $link = null) {
268
269
		if (!key_exists('status', $data)) {
270
			$data['status'] = 1;
271
		}
272
273
		if ($link !== null) {
274
			$data = array_merge($data, ['token' => $link->getToken(true)]);
275
		}
276
277
		return new DataResponse($data, Http::STATUS_OK);
278
	}
279
280
281
	/**
282
	 * send a negative response to a request, with a reason of the failure.
283
	 *
284
	 * @param string $reason
285
	 *
286
	 * @return DataResponse
287
	 */
288
	private function federatedFail($reason) {
289
		$this->miscService->log(2, 'federated fail: ' . $reason);
290
291
		return new DataResponse(
292
			[
293
				'status' => FederatedLink::STATUS_ERROR,
294
				'reason' => $reason
295
			],
296
			Http::STATUS_OK
297
		);
298
	}
299
}