Completed
Pull Request — master (#1917)
by Lukas
40:33 queued 30:26
created

SecurityMiddleware::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
240
						]
241
					);
242
					$response = new RedirectResponse($url);
243
				} else {
244
					$response = new TemplateResponse('core', '403', ['file' => $exception->getMessage()], 'guest');
245
					$response->setStatus($exception->getCode());
246
				}
247
			}
248
249
			$this->logger->debug($exception->getMessage());
250
			return $response;
251
		}
252
253
		throw $exception;
254
	}
255
256
}
257