Passed
Push — master ( ec7e83...51197a )
by Roeland
10:19 queued 12s
created

SecurityMiddleware::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 11
dl 0
loc 23
rs 9.9
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
declare(strict_types=1);
3
/**
4
 * @copyright Copyright (c) 2016, ownCloud, Inc.
5
 *
6
 * @author Bernhard Posselt <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author Stefan Weil <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 * @author Thomas Tanghus <[email protected]>
14
 *
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
32
namespace OC\AppFramework\Middleware\Security;
33
34
use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException;
35
use OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException;
36
use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
37
use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
38
use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException;
39
use OC\AppFramework\Utility\ControllerMethodReflector;
40
use OC\Security\CSP\ContentSecurityPolicyManager;
41
use OC\Security\CSP\ContentSecurityPolicyNonceManager;
42
use OC\Security\CSRF\CsrfTokenManager;
43
use OCP\App\AppPathNotFoundException;
44
use OCP\App\IAppManager;
45
use OCP\AppFramework\Http\ContentSecurityPolicy;
46
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
47
use OCP\AppFramework\Http\RedirectResponse;
48
use OCP\AppFramework\Http\TemplateResponse;
49
use OCP\AppFramework\Middleware;
50
use OCP\AppFramework\Http\Response;
51
use OCP\AppFramework\Http\JSONResponse;
52
use OCP\AppFramework\OCSController;
53
use OCP\IL10N;
54
use OCP\INavigationManager;
55
use OCP\IURLGenerator;
56
use OCP\IRequest;
57
use OCP\ILogger;
58
use OCP\AppFramework\Controller;
59
use OCP\Util;
60
use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
61
62
/**
63
 * Used to do all the authentication and checking stuff for a controller method
64
 * It reads out the annotations of a controller method and checks which if
65
 * security things should be checked and also handles errors in case a security
66
 * check fails
67
 */
68
class SecurityMiddleware extends Middleware {
69
	/** @var INavigationManager */
70
	private $navigationManager;
71
	/** @var IRequest */
72
	private $request;
73
	/** @var ControllerMethodReflector */
74
	private $reflector;
75
	/** @var string */
76
	private $appName;
77
	/** @var IURLGenerator */
78
	private $urlGenerator;
79
	/** @var ILogger */
80
	private $logger;
81
	/** @var bool */
82
	private $isLoggedIn;
83
	/** @var bool */
84
	private $isAdminUser;
85
	/** @var bool */
86
	private $isSubAdmin;
87
	/** @var IAppManager */
88
	private $appManager;
89
	/** @var IL10N */
90
	private $l10n;
91
92
	public function __construct(IRequest $request,
93
								ControllerMethodReflector $reflector,
94
								INavigationManager $navigationManager,
95
								IURLGenerator $urlGenerator,
96
								ILogger $logger,
97
								string $appName,
98
								bool $isLoggedIn,
99
								bool $isAdminUser,
100
								bool $isSubAdmin,
101
								IAppManager $appManager,
102
								IL10N $l10n
103
	) {
104
		$this->navigationManager = $navigationManager;
105
		$this->request = $request;
106
		$this->reflector = $reflector;
107
		$this->appName = $appName;
108
		$this->urlGenerator = $urlGenerator;
109
		$this->logger = $logger;
110
		$this->isLoggedIn = $isLoggedIn;
111
		$this->isAdminUser = $isAdminUser;
112
		$this->isSubAdmin = $isSubAdmin;
113
		$this->appManager = $appManager;
114
		$this->l10n = $l10n;
115
	}
116
117
	/**
118
	 * This runs all the security checks before a method call. The
119
	 * security checks are determined by inspecting the controller method
120
	 * annotations
121
	 * @param Controller $controller the controller
122
	 * @param string $methodName the name of the method
123
	 * @throws SecurityException when a security check fails
124
	 */
125
	public function beforeController($controller, $methodName) {
126
127
		// this will set the current navigation entry of the app, use this only
128
		// for normal HTML requests and not for AJAX requests
129
		$this->navigationManager->setActiveEntry($this->appName);
130
131
		// security checks
132
		$isPublicPage = $this->reflector->hasAnnotation('PublicPage');
133
		if(!$isPublicPage) {
134
			if(!$this->isLoggedIn) {
135
				throw new NotLoggedInException();
136
			}
137
138
			if($this->reflector->hasAnnotation('SubAdminRequired')
139
				&& !$this->isSubAdmin
140
				&& !$this->isAdminUser) {
141
				throw new NotAdminException($this->l10n->t('Logged in user must be an admin or sub admin'));
142
			}
143
			if(!$this->reflector->hasAnnotation('SubAdminRequired')
144
				&& !$this->reflector->hasAnnotation('NoAdminRequired')
145
				&& !$this->isAdminUser) {
146
				throw new NotAdminException($this->l10n->t('Logged in user must be an admin'));
147
			}
148
		}
149
150
		// Check for strict cookie requirement
151
		if($this->reflector->hasAnnotation('StrictCookieRequired') || !$this->reflector->hasAnnotation('NoCSRFRequired')) {
152
			if(!$this->request->passesStrictCookieCheck()) {
153
				throw new StrictCookieMissingException();
154
			}
155
		}
156
		// CSRF check - also registers the CSRF token since the session may be closed later
157
		Util::callRegister();
158
		if(!$this->reflector->hasAnnotation('NoCSRFRequired')) {
159
			/*
160
			 * Only allow the CSRF check to fail on OCS Requests. This kind of
161
			 * hacks around that we have no full token auth in place yet and we
162
			 * do want to offer CSRF checks for web requests.
163
			 *
164
			 * Additionally we allow Bearer authenticated requests to pass on OCS routes.
165
			 * This allows oauth apps (e.g. moodle) to use the OCS endpoints
166
			 */
167
			if(!$this->request->passesCSRFCheck() && !(
168
					$controller instanceof OCSController && (
169
						$this->request->getHeader('OCS-APIREQUEST') === 'true' ||
170
						strpos($this->request->getHeader('Authorization'), 'Bearer ') === 0
171
					)
172
				)) {
173
				throw new CrossSiteRequestForgeryException();
174
			}
175
		}
176
177
		/**
178
		 * Checks if app is enabled (also includes a check whether user is allowed to access the resource)
179
		 * The getAppPath() check is here since components such as settings also use the AppFramework and
180
		 * therefore won't pass this check.
181
		 * If page is public, app does not need to be enabled for current user/visitor
182
		 */
183
		try {
184
			$appPath = $this->appManager->getAppPath($this->appName);
185
		} catch (AppPathNotFoundException $e) {
186
			$appPath = false;
187
		}
188
189
		if ($appPath !== false && !$isPublicPage && !$this->appManager->isEnabledForUser($this->appName)) {
190
			throw new AppNotEnabledException();
191
		}
192
	}
193
194
	/**
195
	 * If an SecurityException is being caught, ajax requests return a JSON error
196
	 * response and non ajax requests redirect to the index
197
	 * @param Controller $controller the controller that is being called
198
	 * @param string $methodName the name of the method that will be called on
199
	 *                           the controller
200
	 * @param \Exception $exception the thrown exception
201
	 * @throws \Exception the passed in exception if it can't handle it
202
	 * @return Response a Response object or null in case that the exception could not be handled
203
	 */
204
	public function afterException($controller, $methodName, \Exception $exception): Response {
205
		if($exception instanceof SecurityException) {
206
			if($exception instanceof StrictCookieMissingException) {
207
				return new RedirectResponse(\OC::$WEBROOT);
208
 			}
209
			if (stripos($this->request->getHeader('Accept'),'html') === false) {
210
				$response = new JSONResponse(
211
					['message' => $exception->getMessage()],
212
					$exception->getCode()
213
				);
214
			} else {
215
				if($exception instanceof NotLoggedInException) {
216
					$params = [];
217
					if (isset($this->request->server['REQUEST_URI'])) {
218
						$params['redirect_url'] = $this->request->server['REQUEST_URI'];
219
					}
220
					$url = $this->urlGenerator->linkToRoute('core.login.showLoginForm', $params);
221
					$response = new RedirectResponse($url);
222
				} else {
223
					$response = new TemplateResponse('core', '403', ['message' => $exception->getMessage()], 'guest');
224
					$response->setStatus($exception->getCode());
225
				}
226
			}
227
228
			$this->logger->logException($exception, [
229
				'level' => ILogger::DEBUG,
230
				'app' => 'core',
231
			]);
232
			return $response;
233
		}
234
235
		throw $exception;
236
	}
237
238
}
239