Completed
Push — master ( b4df57...e6895c )
by Lukas
26s
created

CORSMiddleware   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 118
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 118
rs 10
wmc 15
lcom 1
cbo 6

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B beforeController() 0 18 5
B afterController() 0 22 6
A afterException() 0 13 3
1
<?php
2
/**
3
 * @author Bernhard Posselt <[email protected]>
4
 * @author Lukas Reschke <[email protected]>
5
 * @author Morris Jobke <[email protected]>
6
 * @author Stefan Weil <[email protected]>
7
 *
8
 * @copyright Copyright (c) 2016, ownCloud, Inc.
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
22
 *
23
 */
24
25
namespace OC\AppFramework\Middleware\Security;
26
27
use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
28
use OC\AppFramework\Utility\ControllerMethodReflector;
29
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
30
use OC\User\Session;
31
use OCP\AppFramework\Controller;
32
use OCP\AppFramework\Http;
33
use OCP\AppFramework\Http\JSONResponse;
34
use OCP\AppFramework\Http\Response;
35
use OCP\AppFramework\Middleware;
36
use OCP\IRequest;
37
38
/**
39
 * This middleware sets the correct CORS headers on a response if the
40
 * controller has the @CORS annotation. This is needed for webapps that want
41
 * to access an API and don't run on the same domain, see
42
 * https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
43
 */
44
class CORSMiddleware extends Middleware {
45
46
	/**
47
	 * @var IRequest
48
	 */
49
	private $request;
50
51
	/**
52
	 * @var ControllerMethodReflector
53
	 */
54
	private $reflector;
55
56
	/**
57
	 * @var Session
58
	 */
59
	private $session;
60
61
	/**
62
	 * @param IRequest $request
63
	 * @param ControllerMethodReflector $reflector
64
	 * @param Session $session
65
	 */
66
	public function __construct(IRequest $request,
67
								ControllerMethodReflector $reflector,
68
								Session $session) {
69
		$this->request = $request;
70
		$this->reflector = $reflector;
71
		$this->session = $session;
72
	}
73
74
	/**
75
	 * This is being run in normal order before the controller is being
76
	 * called which allows several modifications and checks
77
	 *
78
	 * @param Controller $controller the controller that is being called
79
	 * @param string $methodName the name of the method that will be called on
80
	 *                           the controller
81
	 * @throws SecurityException
82
	 * @since 6.0.0
83
	 */
84
	public function beforeController($controller, $methodName){
85
		// ensure that @CORS annotated API routes are not used in conjunction
86
		// with session authentication since this enables CSRF attack vectors
87
		if ($this->reflector->hasAnnotation('CORS') &&
88
			!$this->reflector->hasAnnotation('PublicPage'))  {
89
			$user = $this->request->server['PHP_AUTH_USER'];
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...
90
			$pass = $this->request->server['PHP_AUTH_PW'];
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...
91
92
			$this->session->logout();
93
			try {
94
				if (!$this->session->logClientIn($user, $pass, $this->request)) {
95
					throw new SecurityException('CORS requires basic auth', Http::STATUS_UNAUTHORIZED);
96
				}
97
			} catch (PasswordLoginForbiddenException $ex) {
98
				throw new SecurityException('Password login forbidden, use token instead', Http::STATUS_UNAUTHORIZED);
99
			}
100
		}
101
	}
102
103
	/**
104
	 * This is being run after a successful controllermethod call and allows
105
	 * the manipulation of a Response object. The middleware is run in reverse order
106
	 *
107
	 * @param Controller $controller the controller that is being called
108
	 * @param string $methodName the name of the method that will be called on
109
	 *                           the controller
110
	 * @param Response $response the generated response from the controller
111
	 * @return Response a Response object
112
	 * @throws SecurityException
113
	 */
114
	public function afterController($controller, $methodName, Response $response){
115
		// only react if its a CORS request and if the request sends origin and
116
117
		if(isset($this->request->server['HTTP_ORIGIN']) &&
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...
118
			$this->reflector->hasAnnotation('CORS')) {
119
120
			// allow credentials headers must not be true or CSRF is possible
121
			// otherwise
122
			foreach($response->getHeaders() as $header => $value) {
123
				if(strtolower($header) === 'access-control-allow-credentials' &&
124
				   strtolower(trim($value)) === 'true') {
125
					$msg = 'Access-Control-Allow-Credentials must not be '.
126
						   'set to true in order to prevent CSRF';
127
					throw new SecurityException($msg);
128
				}
129
			}
130
131
			$origin = $this->request->server['HTTP_ORIGIN'];
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...
132
			$response->addHeader('Access-Control-Allow-Origin', $origin);
133
		}
134
		return $response;
135
	}
136
137
	/**
138
	 * If an SecurityException is being caught return a JSON error response
139
	 *
140
	 * @param Controller $controller the controller that is being called
141
	 * @param string $methodName the name of the method that will be called on
142
	 *                           the controller
143
	 * @param \Exception $exception the thrown exception
144
	 * @throws \Exception the passed in exception if it can't handle it
145
	 * @return Response a Response object or null in case that the exception could not be handled
146
	 */
147
	public function afterException($controller, $methodName, \Exception $exception){
148
		if($exception instanceof SecurityException){
149
			$response =  new JSONResponse(['message' => $exception->getMessage()]);
150
			if($exception->getCode() !== 0) {
151
				$response->setStatus($exception->getCode());
152
			} else {
153
				$response->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR);
154
			}
155
			return $response;
156
		}
157
158
		throw $exception;
159
	}
160
161
}
162