Completed
Pull Request — master (#7057)
by Damian
08:49
created

TrustedProxyMiddleware::process()   C

Complexity

Conditions 8
Paths 28

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 18
nc 28
nop 2
dl 0
loc 34
rs 5.3846
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control;
4
5
/**
6
 * This middleware will rewrite headers that provide IP and host details from an upstream proxy.
7
 */
8
class TrustedProxyMiddleware implements HTTPMiddleware
9
{
10
11
    private $trustedProxyIPs = null;
12
13
    private $proxyHostHeaders = [
14
        'X-Forwarded-Host'
15
    ];
16
17
    private $proxyIPHeaders = [
18
        'Client-IP',
19
        'X-Forwarded-For'
20
    ];
21
22
    private $proxySchemeHeaders = [
23
        'X-Forwarded-Protocol',
24
        'X-Forwarded-Proto',
25
    ];
26
27
    /**
28
     * Return the comma-separated list of IP ranges that are trusted to provide proxy headers
29
     *
30
     * @return string
31
     */
32
    public function getTrustedProxyIPs()
33
    {
34
        return $this->trustedProxyIPs;
35
    }
36
37
    /**
38
     * Set the comma-separated list of IP ranges that are trusted to provide proxy headers
39
     *
40
     * @param $trustedProxyIPs string
41
     */
42
    public function setTrustedProxyIPs($trustedProxyIPs)
43
    {
44
        $this->trustedProxyIPs = $trustedProxyIPs;
45
    }
46
47
    /**
48
     * Return the comma-separated list of headers from which to lookup the hostname
49
     *
50
     * @return string
51
     */
52
    public function getProxyHostHeaders()
53
    {
54
        return $this->proxyHostHeaders;
55
    }
56
57
    /**
58
     * Set the comma-separated list of headers from which to lookup the hostname
59
     *
60
     * @param $proxyHostHeaders string
61
     */
62
    public function setProxyHostHeaders($proxyHostHeaders)
63
    {
64
        $this->proxyHostHeaders = $proxyHostHeaders;
65
    }
66
67
    /**
68
     * Return the comma-separated list of headers from which to lookup the client IP
69
     *
70
     * @return string
71
     */
72
    public function getProxyIPHeaders()
73
    {
74
        return $this->proxyIPHeaders;
75
    }
76
77
    /**
78
     * Set the comma-separated list of headers from which to lookup the client IP
79
     *
80
     * @param $proxyIPHeaders string
81
     */
82
    public function setProxyIPHeaders($proxyIPHeaders)
83
    {
84
        $this->proxyIPHeaders = $proxyIPHeaders;
85
    }
86
87
    /**
88
     * Return the comma-separated list of headers from which to lookup the client scheme (http/https)
89
     *
90
     * @return string
91
     */
92
    public function getProxySchemeHeaders()
93
    {
94
        return $this->proxySchemeHeaders;
95
    }
96
97
    /**
98
     * Set the comma-separated list of headers from which to lookup the client scheme (http/https)
99
     *
100
     * @param $proxySchemeHeaders string
101
     */
102
    public function setProxySchemeHeaders($proxySchemeHeaders)
103
    {
104
        $this->proxySchemeHeaders = $proxySchemeHeaders;
105
    }
106
107
    public function process(HTTPRequest $request, callable $delegate)
108
    {
109
        // If this is a trust proxy
110
        if ($this->isTrustedProxy($request)) {
111
            // Replace host
112
            foreach ($this->proxyHostHeaders as $header) {
113
                $hostList = $request->getHeader($header);
114
                if ($hostList) {
115
                    $request->setHeader('Host', strtok($hostList, ','));
116
                    break;
117
                }
118
            }
119
120
            // Replace scheme
121
            foreach ($this->proxySchemeHeaders as $header) {
122
                $scheme = $request->getHeader($header);
123
                if ($scheme) {
124
                    $request->setScheme(strtolower($scheme));
125
                    break;
126
                }
127
            }
128
129
            // Replace IP
130
            foreach ($this->proxyIPHeaders as $header) {
131
                $ipHeader = $this->getIPFromHeaderValue($request->getHeader($header));
132
                if ($ipHeader) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ipHeader of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
133
                    $request->setIP($ipHeader);
134
                    break;
135
                }
136
            }
137
        }
138
139
        return $delegate($request);
140
    }
141
142
    /**
143
     * Determine if the current request is coming from a trusted proxy
144
     *
145
     * @return boolean True if the request's source IP is a trusted proxy
146
     */
147
    protected function isTrustedProxy($request)
148
    {
149
        // Disabled
150
        if (empty($this->trustedProxyIPs) || $trustedIPs === 'none') {
0 ignored issues
show
Bug introduced by
The variable $trustedIPs does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
151
            return false;
152
        }
153
154
        // Allow all
155
        if ($trustedIPs === '*') {
156
            return true;
157
        }
158
159
        // Validate IP address
160
        if ($ip = $request->getIP()) {
161
            return IPUtils::checkIP($ip, explode(',', $trustedIPs));
162
        }
163
164
        return false;
165
    }
166
167
    /**
168
     * Extract an IP address from a header value that has been obtained.
169
     * Accepts single IP or comma separated string of IPs
170
     *
171
     * @param string $headerValue The value from a trusted header
172
     * @return string The IP address
173
     */
174
    protected function getIPFromHeaderValue($headerValue)
175
    {
176
        if (strpos($headerValue, ',') !== false) {
177
            //sometimes the IP from a load balancer could be "x.x.x.x, y.y.y.y, z.z.z.z" so we need to find the most
178
            // likely candidate
179
            $ips = explode(',', $headerValue);
180
            foreach ($ips as $ip) {
181
                $ip = trim($ip);
182
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
183
                    return $ip;
184
                } else {
185
                    return null;
186
                }
187
            }
188
        }
189
        return $headerValue;
190
    }
191
}
192