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 RequestHandlerInterface|null $next Next middleware
89     *
90     * @return ResponseInterface
91     */
92    public function __invoke(ServerRequestInterface $request, ?RequestHandlerInterface $next)
93    {
94        if (!$next) {
95            return (new ResponseFactory())->createResponse();
96        }
97
98        $ipAddress = $this->determineClientIpAddress($request);
99        $request = $request->withAttribute($this->attributeName, $ipAddress);
100
101        return $next->handle($request);
102    }
103
104    /**
105     * Find out the client's IP address from the headers available to us
106     *
107     * @param  ServerRequestInterface $request PSR-7 Request
108     * @return string
109     */
110    protected function determineClientIpAddress($request)
111    {
112        $ipAddress = null;
113
114        $serverParams = $request->getServerParams();
115        if (isset($serverParams['REMOTE_ADDR']) && $this->isValidIpAddress($serverParams['REMOTE_ADDR'])) {
116            $ipAddress = $serverParams['REMOTE_ADDR'];
117        }
118
119        if ($this->isCheckProxyHeaders($ipAddress)) {
120            foreach ($this->headersToInspect as $header) {
121                if ($request->hasHeader($header)) {
122                    $ipString = trim(current(explode(',', $request->getHeaderLine($header))));
123                    if ($this->isValidIpAddress($ipString)) {
124                        $ipAddress = $ipString;
125                        break;
126                    }
127                }
128            }
129        }
130
131        return $ipAddress;
132    }
133
134    protected function isCheckProxyHeaders($ipAddress)
135    {
136        $checkProxyHeaders = $this->checkProxyHeaders;
137        if ($checkProxyHeaders && ($this->trustedProxies === true || !empty($this->trustedProxies))) {
138            if ($this->trustedProxies !== true && !in_array($ipAddress, $this->trustedProxies)) {
139                $checkProxyHeaders = false;
140            }
141        }
142        return $checkProxyHeaders;
143    }
144
145    /**
146     * Check that a given string is a valid IP address
147     *
148     * @param  string  $ipString
149     * @return boolean
150     */
151    protected function isValidIpAddress($ipString)
152    {
153        $flags = FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6;
154        if (filter_var($ipString, FILTER_VALIDATE_IP, $flags) === false) {
155            return false;
156        }
157        return true;
158    }
159}