Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Core\Access;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Core\Session\AccountInterface;
|
Chris@0
|
6 use Drupal\Core\Session\SessionConfigurationInterface;
|
Chris@0
|
7 use Symfony\Component\Routing\Route;
|
Chris@0
|
8 use Symfony\Component\HttpFoundation\Request;
|
Chris@0
|
9
|
Chris@0
|
10 /**
|
Chris@0
|
11 * Access protection against CSRF attacks.
|
Chris@0
|
12 */
|
Chris@0
|
13 class CsrfRequestHeaderAccessCheck implements AccessCheckInterface {
|
Chris@0
|
14
|
Chris@0
|
15 /**
|
Chris@0
|
16 * A string key that will used to designate the token used by this class.
|
Chris@0
|
17 */
|
Chris@0
|
18 const TOKEN_KEY = 'X-CSRF-Token request header';
|
Chris@0
|
19
|
Chris@0
|
20 /**
|
Chris@0
|
21 * The session configuration.
|
Chris@0
|
22 *
|
Chris@0
|
23 * @var \Drupal\Core\Session\SessionConfigurationInterface
|
Chris@0
|
24 */
|
Chris@0
|
25 protected $sessionConfiguration;
|
Chris@0
|
26
|
Chris@0
|
27 /**
|
Chris@0
|
28 * The token generator.
|
Chris@0
|
29 *
|
Chris@0
|
30 * @var \Drupal\Core\Access\CsrfTokenGenerator
|
Chris@0
|
31 */
|
Chris@0
|
32 protected $csrfToken;
|
Chris@0
|
33
|
Chris@0
|
34 /**
|
Chris@0
|
35 * Constructs a new rest CSRF access check.
|
Chris@0
|
36 *
|
Chris@0
|
37 * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
|
Chris@0
|
38 * The session configuration.
|
Chris@0
|
39 * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
|
Chris@0
|
40 * The token generator.
|
Chris@0
|
41 */
|
Chris@0
|
42 public function __construct(SessionConfigurationInterface $session_configuration, CsrfTokenGenerator $csrf_token) {
|
Chris@0
|
43 $this->sessionConfiguration = $session_configuration;
|
Chris@0
|
44 $this->csrfToken = $csrf_token;
|
Chris@0
|
45 }
|
Chris@0
|
46
|
Chris@0
|
47 /**
|
Chris@0
|
48 * {@inheritdoc}
|
Chris@0
|
49 */
|
Chris@0
|
50 public function applies(Route $route) {
|
Chris@0
|
51 $requirements = $route->getRequirements();
|
Chris@0
|
52 // Check for current requirement _csrf_request_header_token and deprecated
|
Chris@0
|
53 // REST requirement.
|
Chris@0
|
54 $applicable_requirements = [
|
Chris@0
|
55 '_csrf_request_header_token',
|
Chris@0
|
56 // @todo Remove _access_rest_csrf in Drupal 9.0.0.
|
Chris@0
|
57 '_access_rest_csrf',
|
Chris@0
|
58 ];
|
Chris@0
|
59 $requirement_keys = array_keys($requirements);
|
Chris@0
|
60
|
Chris@0
|
61 if (array_intersect($applicable_requirements, $requirement_keys)) {
|
Chris@0
|
62 if (isset($requirements['_method'])) {
|
Chris@0
|
63 // There could be more than one method requirement separated with '|'.
|
Chris@0
|
64 $methods = explode('|', $requirements['_method']);
|
Chris@0
|
65 // CSRF protection only applies to write operations, so we can filter
|
Chris@0
|
66 // out any routes that require reading methods only.
|
Chris@0
|
67 $write_methods = array_diff($methods, ['GET', 'HEAD', 'OPTIONS', 'TRACE']);
|
Chris@0
|
68 if (empty($write_methods)) {
|
Chris@0
|
69 return FALSE;
|
Chris@0
|
70 }
|
Chris@0
|
71 }
|
Chris@0
|
72 // No method requirement given, so we run this access check to be on the
|
Chris@0
|
73 // safe side.
|
Chris@0
|
74 return TRUE;
|
Chris@0
|
75 }
|
Chris@0
|
76 }
|
Chris@0
|
77
|
Chris@0
|
78 /**
|
Chris@0
|
79 * Checks access.
|
Chris@0
|
80 *
|
Chris@0
|
81 * @param \Symfony\Component\HttpFoundation\Request $request
|
Chris@0
|
82 * The request object.
|
Chris@0
|
83 * @param \Drupal\Core\Session\AccountInterface $account
|
Chris@0
|
84 * The currently logged in account.
|
Chris@0
|
85 *
|
Chris@0
|
86 * @return \Drupal\Core\Access\AccessResultInterface
|
Chris@0
|
87 * The access result.
|
Chris@0
|
88 */
|
Chris@0
|
89 public function access(Request $request, AccountInterface $account) {
|
Chris@0
|
90 $method = $request->getMethod();
|
Chris@0
|
91
|
Chris@17
|
92 // Read-only operations are always allowed.
|
Chris@17
|
93 if (in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'], TRUE)) {
|
Chris@17
|
94 return AccessResult::allowed();
|
Chris@17
|
95 }
|
Chris@17
|
96
|
Chris@0
|
97 // This check only applies if
|
Chris@17
|
98 // 1. the user was successfully authenticated and
|
Chris@17
|
99 // 2. the request comes with a session cookie.
|
Chris@17
|
100 if ($account->isAuthenticated()
|
Chris@0
|
101 && $this->sessionConfiguration->hasSession($request)
|
Chris@0
|
102 ) {
|
Chris@0
|
103 if (!$request->headers->has('X-CSRF-Token')) {
|
Chris@0
|
104 return AccessResult::forbidden()->setReason('X-CSRF-Token request header is missing')->setCacheMaxAge(0);
|
Chris@0
|
105 }
|
Chris@0
|
106 $csrf_token = $request->headers->get('X-CSRF-Token');
|
Chris@0
|
107 // @todo Remove validate call using 'rest' in 8.3.
|
Chris@0
|
108 // Kept here for sessions active during update.
|
Chris@0
|
109 if (!$this->csrfToken->validate($csrf_token, self::TOKEN_KEY)
|
Chris@0
|
110 && !$this->csrfToken->validate($csrf_token, 'rest')) {
|
Chris@0
|
111 return AccessResult::forbidden()->setReason('X-CSRF-Token request header is invalid')->setCacheMaxAge(0);
|
Chris@0
|
112 }
|
Chris@0
|
113 }
|
Chris@0
|
114 // Let other access checkers decide if the request is legit.
|
Chris@0
|
115 return AccessResult::allowed()->setCacheMaxAge(0);
|
Chris@0
|
116 }
|
Chris@0
|
117
|
Chris@0
|
118 }
|