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