Completed
Push — master ( e4992c...6d0a35 )
by
unknown
10:42
created

SecurityMiddleware::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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