Completed
Pull Request — master (#479)
by Lukas
33:52 queued 25:33
created

SecurityMiddleware::beforeController()   C

Complexity

Conditions 12
Paths 35

Size

Total Lines 45
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 18
c 1
b 0
f 0
nc 35
nop 2
dl 0
loc 45
rs 5.1612

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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) 2016, ownCloud, Inc.
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
29
namespace OC\AppFramework\Middleware\Security;
30
31
use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException;
32
use OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException;
33
use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
34
use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
35
use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException;
36
use OC\AppFramework\Utility\ControllerMethodReflector;
37
use OC\Security\CSP\ContentSecurityPolicyManager;
38
use OCP\AppFramework\Http\ContentSecurityPolicy;
39
use OCP\AppFramework\Http\RedirectResponse;
40
use OCP\AppFramework\Http\TemplateResponse;
41
use OCP\AppFramework\Middleware;
42
use OCP\AppFramework\Http\Response;
43
use OCP\AppFramework\Http\JSONResponse;
44
use OCP\INavigationManager;
45
use OCP\IURLGenerator;
46
use OCP\IRequest;
47
use OCP\ILogger;
48
use OCP\AppFramework\Controller;
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 $isLoggedIn;
73
	/** @var bool */
74
	private $isAdminUser;
75
	/** @var ContentSecurityPolicyManager */
76
	private $contentSecurityPolicyManager;
77
78
	/**
79
	 * @param IRequest $request
80
	 * @param ControllerMethodReflector $reflector
81
	 * @param INavigationManager $navigationManager
82
	 * @param IURLGenerator $urlGenerator
83
	 * @param ILogger $logger
84
	 * @param string $appName
85
	 * @param bool $isLoggedIn
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
								$appName,
95
								$isLoggedIn,
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->isLoggedIn = $isLoggedIn;
105
		$this->isAdminUser = $isAdminUser;
106
		$this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
107
	}
108
109
110
	/**
111
	 * This runs all the security checks before a method call. The
112
	 * security checks are determined by inspecting the controller method
113
	 * annotations
114
	 * @param string $controller the controllername or string
115
	 * @param string $methodName the name of the method
116
	 * @throws SecurityException when a security check fails
117
	 */
118
	public function beforeController($controller, $methodName) {
119
120
		// this will set the current navigation entry of the app, use this only
121
		// for normal HTML requests and not for AJAX requests
122
		$this->navigationManager->setActiveEntry($this->appName);
123
124
		// security checks
125
		$isPublicPage = $this->reflector->hasAnnotation('PublicPage');
126
		if(!$isPublicPage) {
127
			if(!$this->isLoggedIn) {
128
				throw new NotLoggedInException();
129
			}
130
131
			if(!$this->reflector->hasAnnotation('NoAdminRequired')) {
132
				if(!$this->isAdminUser) {
133
					throw new NotAdminException();
134
				}
135
			}
136
		}
137
138
		// Check for strict cookie requirement
139
		if($this->reflector->hasAnnotation('StrictCookieRequired') || !$this->reflector->hasAnnotation('NoCSRFRequired')) {
140
			if(!$this->request->passesStrictCookieCheck()) {
141
				throw new StrictCookieMissingException();
142
			}
143
		}
144
		// CSRF check - also registers the CSRF token since the session may be closed later
145
		Util::callRegister();
146
		if(!$this->reflector->hasAnnotation('NoCSRFRequired')) {
147
			if(!$this->request->passesCSRFCheck()) {
148
				throw new CrossSiteRequestForgeryException();
149
			}
150
		}
151
152
		/**
153
		 * FIXME: Use DI once available
154
		 * Checks if app is enabled (also includes a check whether user is allowed to access the resource)
155
		 * The getAppPath() check is here since components such as settings also use the AppFramework and
156
		 * therefore won't pass this check.
157
		 */
158
		if(\OC_App::getAppPath($this->appName) !== false && !\OC_App::isEnabled($this->appName)) {
159
			throw new AppNotEnabledException();
160
		}
161
162
	}
163
164
	/**
165
	 * Performs the default CSP modifications that may be injected by other
166
	 * applications
167
	 *
168
	 * @param Controller $controller
169
	 * @param string $methodName
170
	 * @param Response $response
171
	 * @return Response
172
	 */
173
	public function afterController($controller, $methodName, Response $response) {
174
		$policy = !is_null($response->getContentSecurityPolicy()) ? $response->getContentSecurityPolicy() : new ContentSecurityPolicy();
175
176
		$defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy();
177
		$defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy);
178
179
		$response->setContentSecurityPolicy($defaultPolicy);
180
181
		return $response;
182
	}
183
184
	/**
185
	 * If an SecurityException is being caught, ajax requests return a JSON error
186
	 * response and non ajax requests redirect to the index
187
	 * @param Controller $controller the controller that is being called
188
	 * @param string $methodName the name of the method that will be called on
189
	 *                           the controller
190
	 * @param \Exception $exception the thrown exception
191
	 * @throws \Exception the passed in exception if it can't handle it
192
	 * @return Response a Response object or null in case that the exception could not be handled
193
	 */
194
	public function afterException($controller, $methodName, \Exception $exception) {
195
		if($exception instanceof SecurityException) {
196
			if($exception instanceof StrictCookieMissingException) {
197
				return new RedirectResponse(\OC::$WEBROOT);
198
 			}
199
			if (stripos($this->request->getHeader('Accept'),'html') === false) {
200
				$response = new JSONResponse(
201
					array('message' => $exception->getMessage()),
202
					$exception->getCode()
203
				);
204
			} else {
205
				if($exception instanceof NotLoggedInException) {
206
					$url = $this->urlGenerator->linkToRoute(
207
						'core.login.showLoginForm',
208
						[
209
							'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...
210
						]
211
					);
212
					$response = new RedirectResponse($url);
213
				} else {
214
					$response = new TemplateResponse('core', '403', ['file' => $exception->getMessage()], 'guest');
215
					$response->setStatus($exception->getCode());
216
				}
217
			}
218
219
			$this->logger->debug($exception->getMessage());
220
			return $response;
221
		}
222
223
		throw $exception;
224
	}
225
226
}
227