Completed
Push — master ( abc7b1...9cce5e )
by Phil
62:45 queued 51:52
created

SecurityMiddleware::afterException()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 43
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 27
nc 5
nop 3
dl 0
loc 43
rs 6.7272
c 0
b 0
f 0
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) 2017, 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
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\Utility\ControllerMethodReflector;
36
use OC\Core\Controller\LoginController;
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
		// CSRF check - also registers the CSRF token since the session may be closed later
139
		Util::callRegister();
140
		if(!$this->reflector->hasAnnotation('NoCSRFRequired')) {
141
			if(!$this->request->passesCSRFCheck()) {
142
				throw new CrossSiteRequestForgeryException();
143
			}
144
		}
145
146
		/**
147
		 * FIXME: Use DI once available
148
		 * Checks if app is enabled (also includes a check whether user is allowed to access the resource)
149
		 * The getAppPath() check is here since components such as settings also use the AppFramework and
150
		 * therefore won't pass this check.
151
		 */
152
		if(\OC_App::getAppPath($this->appName) !== false && !\OC_App::isEnabled($this->appName)) {
153
			throw new AppNotEnabledException();
154
		}
155
156
	}
157
158
	/**
159
	 * Performs the default CSP modifications that may be injected by other
160
	 * applications
161
	 *
162
	 * @param Controller $controller
163
	 * @param string $methodName
164
	 * @param Response $response
165
	 * @return Response
166
	 */
167
	public function afterController($controller, $methodName, Response $response) {
168
		$policy = !is_null($response->getContentSecurityPolicy()) ? $response->getContentSecurityPolicy() : new ContentSecurityPolicy();
169
170
		$defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy();
171
		$defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy);
172
173
		$response->setContentSecurityPolicy($defaultPolicy);
174
175
		return $response;
176
	}
177
178
	/**
179
	 * If an SecurityException is being caught, ajax requests return a JSON error
180
	 * response and non ajax requests redirect to the index
181
	 * @param Controller $controller the controller that is being called
182
	 * @param string $methodName the name of the method that will be called on
183
	 *                           the controller
184
	 * @param \Exception $exception the thrown exception
185
	 * @throws \Exception the passed in exception if it can't handle it
186
	 * @return Response a Response object or null in case that the exception could not be handled
187
	 */
188
	public function afterException($controller, $methodName, \Exception $exception) {
189
		if($exception instanceof SecurityException) {
190
191
			if (stripos($this->request->getHeader('Accept'),'html') === false) {
192
				$response = new JSONResponse(
193
					['message' => $exception->getMessage()],
194
					$exception->getCode()
195
				);
196
			} else {
197
				if($exception instanceof NotLoggedInException) {
198
					$url = $this->urlGenerator->linkToRoute(
199
						'core.login.showLoginForm',
200
						[
201
							'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...
202
						]
203
					);
204
					$response = new RedirectResponse($url);
205
				} elseif (
206
					$methodName === 'tryLogin'
207
					&& $exception instanceof CrossSiteRequestForgeryException
208
					&& $controller instanceof LoginController
209
				) {
210
					$this->logger->debug($exception->getMessage());
211
					$controller->getSession()->set(
212
						'loginMessages',
213
						[
214
							['csrf_error'],
215
							[]
216
						]
217
					);
218
					return $controller->showLoginForm(null, null, null);
219
				} else {
220
					$response = new TemplateResponse('core', '403', ['file' => $exception->getMessage()], 'guest');
221
					$response->setStatus($exception->getCode());
222
				}
223
			}
224
225
			$this->logger->debug($exception->getMessage());
226
			return $response;
227
		}
228
229
		throw $exception;
230
	}
231
232
}
233