1<?php
2/**
3 * @author Björn Schießle <bjoern@schiessle.org>
4 * @author Lukas Reschke <lukas@statuscode.ch>
5 * @author Robin Appelman <icewind@owncloud.com>
6 * @author Roeland Jago Douma <rullzer@owncloud.com>
7 * @author Roeland Jago Douma <rullzer@users.noreply.github.com>
8 * @author Thomas Müller <thomas.mueller@tmit.eu>
9 *
10 * @copyright Copyright (c) 2018, ownCloud GmbH
11 * @license AGPL-3.0
12 *
13 * This code is free software: you can redistribute it and/or modify
14 * it under the terms of the GNU Affero General Public License, version 3,
15 * as published by the Free Software Foundation.
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, version 3,
23 * along with this program.  If not, see <http://www.gnu.org/licenses/>
24 *
25 */
26
27namespace OCA\Federation\Controller;
28
29use OCA\Federation\DbHandler;
30use OCA\Federation\TrustedServers;
31use OCP\AppFramework\Http;
32use OCP\AppFramework\OCSController;
33use OCP\BackgroundJob\IJobList;
34use OCP\ILogger;
35use OCP\IRequest;
36use OCP\Security\ISecureRandom;
37
38/**
39 * Class OCSAuthAPIController
40 *
41 * OCS API end-points to exchange shared secret between two connected ownClouds
42 *
43 * @package OCA\Federation\Controller
44 */
45class OCSAuthAPIController extends OCSController {
46
47	/** @var ISecureRandom  */
48	private $secureRandom;
49
50	/** @var IJobList */
51	private $jobList;
52
53	/** @var TrustedServers */
54	private $trustedServers;
55
56	/** @var DbHandler */
57	private $dbHandler;
58
59	/** @var ILogger */
60	private $logger;
61
62	/**
63	 * OCSAuthAPI constructor.
64	 *
65	 * @param string $appName
66	 * @param IRequest $request
67	 * @param ISecureRandom $secureRandom
68	 * @param IJobList $jobList
69	 * @param TrustedServers $trustedServers
70	 * @param DbHandler $dbHandler
71	 * @param ILogger $logger
72	 */
73	public function __construct(
74		$appName,
75		IRequest $request,
76		ISecureRandom $secureRandom,
77		IJobList $jobList,
78		TrustedServers $trustedServers,
79		DbHandler $dbHandler,
80		ILogger $logger
81	) {
82		parent::__construct($appName, $request);
83
84		$this->secureRandom = $secureRandom;
85		$this->jobList = $jobList;
86		$this->trustedServers = $trustedServers;
87		$this->dbHandler = $dbHandler;
88		$this->logger = $logger;
89	}
90
91	/**
92	 * @NoCSRFRequired
93	 * @PublicPage
94	 *
95	 * request received to ask remote server for a shared secret
96	 *
97	 * @param string $url
98	 * @param string $token
99	 * @return array()
100	 */
101	public function requestSharedSecret($url, $token) {
102		if ($this->trustedServers->isTrustedServer($url) === false) {
103			$this->logger->error('remote server not trusted (' . $url . ') while requesting shared secret', ['app' => 'federation']);
104			return ['statuscode' => Http::STATUS_FORBIDDEN];
105		}
106
107		// if both server initiated the exchange of the shared secret the greater
108		// token wins
109		$localToken = $this->dbHandler->getToken($url);
110		if (\strcmp($localToken, $token) > 0) {
111			$this->logger->info(
112				'remote server (' . $url . ') presented lower token. We will initiate the exchange of the shared secret.',
113				['app' => 'federation']
114			);
115			return ['statuscode' => Http::STATUS_FORBIDDEN];
116		}
117
118		// we ask for the shared secret so we no longer have to ask the other server
119		// to request the shared secret
120		$this->jobList->remove(
121			'OCA\Federation\BackgroundJob\RequestSharedSecret',
122			[
123				'url' => $url,
124				'token' => $localToken
125			]
126		);
127
128		$this->jobList->add(
129			'OCA\Federation\BackgroundJob\GetSharedSecret',
130			[
131				'url' => $url,
132				'token' => $token,
133			]
134		);
135
136		return ['statuscode' => Http::STATUS_OK];
137	}
138
139	/**
140	 * @NoCSRFRequired
141	 * @PublicPage
142	 *
143	 * create shared secret and return it
144	 *
145	 * @param string $url
146	 * @param string $token
147	 * @return array
148	 */
149	public function getSharedSecret($url, $token) {
150		if ($this->trustedServers->isTrustedServer($url) === false) {
151			$this->logger->error('remote server not trusted (' . $url . ') while getting shared secret', ['app' => 'federation']);
152			return ['statuscode' => Http::STATUS_FORBIDDEN];
153		}
154
155		if ($this->isValidToken($url, $token) === false) {
156			$expectedToken = $this->dbHandler->getToken($url);
157			$this->logger->error(
158				'remote server (' . $url . ') didn\'t send a valid token (got "' . $token . '" but expected "'. $expectedToken . '") while getting shared secret',
159				['app' => 'federation']
160			);
161			return ['statuscode' => Http::STATUS_FORBIDDEN];
162		}
163
164		$sharedSecret = $this->secureRandom->generate(32);
165
166		$this->trustedServers->addSharedSecret($url, $sharedSecret);
167		// reset token after the exchange of the shared secret was successful
168		$this->dbHandler->addToken($url, '');
169
170		return ['statuscode' => Http::STATUS_OK,
171			'data' => ['sharedSecret' => $sharedSecret]];
172	}
173
174	protected function isValidToken($url, $token) {
175		$storedToken = $this->dbHandler->getToken($url);
176		return \hash_equals($storedToken, $token);
177	}
178}
179