Gateway::send()   D
last analyzed

Complexity

Conditions 24
Paths 16

Size

Total Lines 103
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 600

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 66
c 2
b 0
f 1
dl 0
loc 103
ccs 0
cts 81
cp 0
rs 4.1666
cc 24
nc 16
nop 3
crap 600

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * SPDX-FileCopyrightText: 2024 Christoph Wurst <[email protected]>
7
 * SPDX-License-Identifier: AGPL-3.0-or-later
8
 */
9
10
namespace OCA\TwoFactorGateway\Provider\Channel\Signal;
11
12
use OCA\TwoFactorGateway\Exception\MessageTransmissionException;
13
use OCA\TwoFactorGateway\Provider\FieldDefinition;
14
use OCA\TwoFactorGateway\Provider\Gateway\AGateway;
15
use OCA\TwoFactorGateway\Provider\Settings;
16
use OCP\AppFramework\Utility\ITimeFactory;
17
use OCP\Http\Client\IClientService;
18
use OCP\IAppConfig;
19
use Psr\Log\LoggerInterface;
20
use Symfony\Component\Console\Helper\QuestionHelper;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Output\OutputInterface;
23
use Symfony\Component\Console\Question\Question;
24
25
/**
26
 * An integration of https://gitlab.com/morph027/signal-web-gateway
27
 *
28
 * @method string getUrl()
29
 * @method AGateway setUrl(string $url)
30
 * @method string getAccount()
31
 * @method AGateway setAccount(string $account)
32
 */
33
class Gateway extends AGateway {
34
	public const ACCOUNT_UNNECESSARY = 'unneccessary';
35
36 1
	public function __construct(
37
		public IAppConfig $appConfig,
38
		private IClientService $clientService,
39
		private ITimeFactory $timeFactory,
40
		private LoggerInterface $logger,
41
	) {
42 1
		parent::__construct($appConfig);
43
	}
44
45 1
	#[\Override]
46
	public function createSettings(): Settings {
47 1
		return new Settings(
48 1
			name: 'Signal',
49 1
			instructions: 'The gateway can send authentication to your Signal mobile and deskop app.',
50 1
			fields: [
51 1
				new FieldDefinition(
52 1
					field: 'url',
53 1
					prompt: 'Please enter the URL of the Signal gateway (leave blank to use default):',
54 1
					default: 'http://localhost:5000',
55 1
				),
56 1
				new FieldDefinition(
57 1
					field: 'account',
58 1
					prompt: 'Please enter the account (phone-number) of the sending signal account (leave blank if a phone-number is not required):',
59 1
				),
60 1
			]
61 1
		);
62
	}
63
64
	#[\Override]
65
	public function send(string $identifier, string $message, array $extra = []): void {
66
		$client = $this->clientService->newClient();
67
		// determine type of gateway
68
69
		// test for native signal-cli JSON RPC.
70
		$response = $client->post(
71
			$this->getUrl() . '/api/v1/rpc',
72
			[
73
				'http_errors' => false,
74
				'json' => [
75
					'jsonrpc' => '2.0',
76
					'method' => 'version',
77
					'id' => 'version_' . $this->timeFactory->getTime(),
78
				],
79
			]);
80
		if ($response->getStatusCode() === 200 || $response->getStatusCode() === 201) {
81
			// native signal-cli JSON RPC.
82
83
			// Groups have to be detected and passed with the "group-id" parameter. We assume a group is given as base64 encoded string
84
			$groupId = base64_decode($identifier, strict: true);
85
			$isGroup = $groupId !== false && base64_encode($groupId) === $identifier;
86
			$recipientKey = $isGroup ? 'group-id' : 'recipient';
87
			$params = [
88
				'message' => $message,
89
				$recipientKey => $identifier,
90
				'account' => $this->getAccount(), // mandatory for native RPC API
91
			];
92
			$response = $response = $client->post(
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
93
				$this->getUrl() . '/api/v1/rpc',
94
				[
95
					'json' => [
96
						'jsonrpc' => '2.0',
97
						'method' => 'send',
98
						'id' => 'code_' . $this->timeFactory->getTime(),
99
						'params' => $params,
100
					],
101
				]);
102
			$body = $response->getBody();
103
			$json = json_decode($body, true);
0 ignored issues
show
Bug introduced by
It seems like $body can also be of type null and resource; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

103
			$json = json_decode(/** @scrutinizer ignore-type */ $body, true);
Loading history...
104
			$statusCode = $response->getStatusCode();
105
			// The 201 "created" is probably a bug.
106
			if ($statusCode < 200 || $statusCode >= 300 || is_null($json) || !is_array($json) || ($json['jsonrpc'] ?? null) != '2.0' || !isset($json['result']['timestamp'])) {
107
				throw new MessageTransmissionException("error reported by Signal gateway, status=$statusCode, body=$body}");
108
			}
109
		} else {
110
			// Try gateway in the style of https://gitlab.com/morph027/signal-cli-dbus-rest-api
111
			$response = $client->get($this->getUrl() . '/v1/about');
112
			if ($response->getStatusCode() === 200) {
113
				// Not so "ńew style" gateway, see
114
				// https://gitlab.com/morph027/signal-cli-dbus-rest-api
115
				// https://gitlab.com/morph027/python-signal-cli-rest-api
116
				// https://github.com/bbernhard/signal-cli-rest-api
117
				$body = $response->getBody();
118
				$json = json_decode($body, true);
119
				$versions = $json['versions'] ?? [];
120
				if (is_array($versions) && in_array('v2', $versions)) {
121
					$json = [
122
						'recipients' => $identifier,
123
						'message' => $message,
124
					];
125
					$account = $this->getAccount();
126
					if ($account != self::ACCOUNT_UNNECESSARY) {
127
						$json['account'] = $account;
128
					}
129
					$response = $client->post(
130
						$this->getUrl() . '/v2/send',
131
						[
132
							'json' => $json,
133
						]
134
					);
135
				} else {
136
					$response = $client->post(
137
						$this->getUrl() . '/v1/send/' . $identifier,
138
						[
139
							'json' => [ 'message' => $message ],
140
						]
141
					);
142
				}
143
				$body = (string)$response->getBody();
144
				$json = json_decode($body, true);
145
				$status = $response->getStatusCode();
146
				if ($status !== 201 || is_null($json) || !is_array($json) || !isset($json['timestamp'])) {
147
					throw new MessageTransmissionException("error reported by Signal gateway, status=$status, body=$body}");
148
				}
149
			} else {
150
				// Try old deprecated gateway https://gitlab.com/morph027/signal-web-gateway
151
				$response = $client->post(
152
					$this->getUrl() . '/v1/send/' . $identifier,
153
					[
154
						'body' => [
155
							'to' => $identifier,
156
							'message' => $message,
157
						],
158
						'json' => [ 'message' => $message ],
159
					]
160
				);
161
				$body = (string)$response->getBody();
162
				$json = json_decode($body, true);
163
164
				$status = $response->getStatusCode();
165
				if ($status !== 200 || is_null($json) || !is_array($json) || !isset($json['success']) || $json['success'] !== true) {
166
					throw new MessageTransmissionException("error reported by Signal gateway, status=$status, body=$body}");
167
				}
168
			}
169
		}
170
	}
171
172
	#[\Override]
173
	public function cliConfigure(InputInterface $input, OutputInterface $output): int {
174
		$settings = $this->getSettings();
175
		$helper = new QuestionHelper();
176
		$urlQuestion = new Question($settings->fields[0]->prompt, $settings->fields[0]->default);
177
		$url = $helper->ask($input, $output, $urlQuestion);
178
		$output->writeln("Using $url.");
179
180
		$this->setUrl($url);
181
182
		$accountQuestion = new Question($settings->fields[1]->prompt, $settings->fields[1]->default);
183
		$account = $helper->ask($input, $output, $accountQuestion);
184
		if ($account == '') {
185
			$account = self::ACCOUNT_UNNECESSARY;
186
			$output->writeln('A signal account is not needed, assuming it is hardcoded into the signal gateway server.');
187
		} else {
188
			$output->writeln("Using $account.");
189
		}
190
191
		$this->setAccount($account);
192
193
		return 0;
194
	}
195
}
196