Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.38% covered (warning)
84.38%
27 / 32
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
IpAddress
84.38% covered (warning)
84.38%
27 / 32
20.00% covered (danger)
20.00%
1 / 5
21.53
0.00% covered (danger)
0.00%
0 / 1
 __construct
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
3.33
 __invoke
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 determineClientIpAddress
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
7
 isCheckProxyHeaders
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
6.29
 isValidIpAddress
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2
3/**
4 * @copyright MIT
5 * derived from akrabat/rka-ip-address-middleware via composer.phar
6 **/
7
8namespace BO\Slim\Middleware;
9
10use Psr\Http\Message\ServerRequestInterface;
11use Psr\Http\Message\ResponseInterface;
12use Psr\Http\Server\RequestHandlerInterface;
13use BO\Slim\Factory\ResponseFactory;
14
15class IpAddress
16{
17    /**
18     * Enable checking of proxy headers (X-Forwarded-For to determined client IP.
19     *
20     * Defaults to false as only $_SERVER['REMOTE_ADDR'] is a trustworthy source
21     * of IP address.
22     *
23     * @var bool
24     */
25    protected $checkProxyHeaders;
26
27    /**
28     * List of trusted proxy IP addresses
29     *
30     * If not empty, then one of these IP addresses must be in $_SERVER['REMOTE_ADDR']
31     * in order for the proxy headers to be looked at.
32     * If TRUE then trust every proxy
33     *
34     * @var array|true
35     */
36    protected $trustedProxies;
37
38    /**
39     * Name of the attribute added to the ServerRequest object
40     *
41     * @var string
42     */
43    protected $attributeName = 'ip_address';
44
45    /**
46     * List of proxy headers inspected for the client IP address
47     *
48     * @var array
49     */
50    protected $headersToInspect = [
51        'X-Remote-Ip',
52        'X-Forwarded-For',
53        'X-Forwarded',
54        'X-Cluster-Client-Ip',
55        'Client-Ip',
56    ];
57
58    /**
59     * Constructor
60     *
61     * @param bool $checkProxyHeaders Whether to use proxy headers to determine client IP
62     * @param $trustedProxies   List of IP addresses of trusted proxies or TRUE if all proxies should be trusted
63     * @param string $attributeName   Name of attribute added to ServerRequest object
64     * @param array $headersToInspect List of headers to inspect
65     */
66    public function __construct(
67        $checkProxyHeaders = false,
68        $trustedProxies = [],
69        $attributeName = null,
70        array $headersToInspect = []
71    ) {
72        $this->checkProxyHeaders = $checkProxyHeaders;
73        $this->trustedProxies = $trustedProxies;
74
75        if ($attributeName) {
76            $this->attributeName = $attributeName;
77        }
78        if (!empty($headersToInspect)) {
79            $this->headersToInspect = $headersToInspect;
80        }
81    }
82
83    /**
84     * Set the "$attributeName" attribute to the client's IP address as determined from
85     * the proxy header (X-Forwarded-For or from $_SERVER['REMOTE_ADDR']
86     *
87     * @param ServerRequestInterface $request PSR7 request
88     * @param ResponseInterface $response     PSR7 response
89     * @param callable $next                  Next middleware
90     *
91     * @return ResponseInterface
92     */
93    public function __invoke(ServerRequestInterface $request, ?RequestHandlerInterface $next)
94    {
95        if (!$next) {
96            return (new ResponseFactory())->createResponse();
97        }
98
99        $ipAddress = $this->determineClientIpAddress($request);
100        $request = $request->withAttribute($this->attributeName, $ipAddress);
101
102        return $next->handle($request);
103    }
104
105    /**
106     * Find out the client's IP address from the headers available to us
107     *
108     * @param  ServerRequestInterface $request PSR-7 Request
109     * @return string
110     */
111    protected function determineClientIpAddress($request)
112    {
113        $ipAddress = null;
114
115        $serverParams = $request->getServerParams();
116        if (isset($serverParams['REMOTE_ADDR']) && $this->isValidIpAddress($serverParams['REMOTE_ADDR'])) {
117            $ipAddress = $serverParams['REMOTE_ADDR'];
118        }
119
120        if ($this->isCheckProxyHeaders($ipAddress)) {
121            foreach ($this->headersToInspect as $header) {
122                if ($request->hasHeader($header)) {
123                    $ipString = trim(current(explode(',', $request->getHeaderLine($header))));
124                    if ($this->isValidIpAddress($ipString)) {
125                        $ipAddress = $ipString;
126                        break;
127                    }
128                }
129            }
130        }
131
132        return $ipAddress;
133    }
134
135    protected function isCheckProxyHeaders($ipAddress)
136    {
137        $checkProxyHeaders = $this->checkProxyHeaders;
138        if ($checkProxyHeaders && ($this->trustedProxies === true || !empty($this->trustedProxies))) {
139            if ($this->trustedProxies !== true && !in_array($ipAddress, $this->trustedProxies)) {
140                $checkProxyHeaders = false;
141            }
142        }
143        return $checkProxyHeaders;
144    }
145
146    /**
147     * Check that a given string is a valid IP address
148     *
149     * @param  string  $ipString
150     * @return boolean
151     */
152    protected function isValidIpAddress($ipString)
153    {
154        $flags = FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6;
155        if (filter_var($ipString, FILTER_VALIDATE_IP, $flags) === false) {
156            return false;
157        }
158        return true;
159    }
160}