GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( ec6db5...676741 )
by Carsten
14:46
created

IpstackMiddleware::assertClientIp()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 12
cts 12
cp 1
rs 9.584
c 0
b 0
f 0
cc 4
nc 4
nop 1
crap 4
1
<?php
2
namespace Germania\IpstackClient;
3
4
use Germania\IpstackClient\IpstackClientInterface;
5
use Germania\IpstackClient\IpstackExceptionInterface;
6
7
use GuzzleHttp\Psr7\Response as GuzzleResponse;
8
9
use Psr\Http\Server\MiddlewareInterface;
10
use Psr\Http\Server\RequestHandlerInterface;
11
use Psr\Http\Message\ResponseInterface;
12
use Psr\Http\Message\ServerRequestInterface;
13
14
use Psr\Log\LoggerInterface;
15
use Psr\Log\LoggerAwareTrait;
16
use Psr\Log\NullLogger;
17
18
19
/**
20
 * This Slim-style "Double Pass" middleware finds out the country where the client comes from
21
 * and stores the country code (DE or CH) with PSR-7 Request attribute.
22
 *
23
 * Requirement:
24
 *
25
 *     This middleware requires a ServerRequest attribute called "ip_address"
26
 *     as provided by akrabat's Slim Client IP address middleware 
27
 *     - which therefore must be executed before this one!
28
 *     
29
 *     https://github.com/akrabat/ip-address-middleware
30
 * 
31
 * Basic conecpts:
32
 *
33
 * IP to Geolocation:
34
 *     This class requires an IpstackClient instance provided by germania-kg/ipstack.
35
 *     which asks the "IP to Geolocation" API from ipstack (https://ipstack.com). 
36
 *     Since we currently are using the "free plan", usage is limited to 10.000 API calls per month.
37
 */
38
class IpstackMiddleware implements MiddlewareInterface
39
{
40
    use LoggerAwareTrait;
41
42
43
    /**
44
     * Maps ipstack response fields to Request attribute Names. 
45
     * 
46
     * Array keys are ipstack fields, values are attribute names,
47
     * for example:
48
     *
49
     *     array(
50
     *          "country_code" => "X-IpstackCountryCode",
51
     *          "language"     => "X-IpstackLanguage"
52
     *     );
53
     * 
54
     * @var array
55
     */
56
    public $ipstack_attributes = array();
57
58
59
    /**
60
     * Specifies the ipstack response fields that will be requested always.
61
     * 
62
     * @see https://ipstack.com/documentation#fields
63
     * @var array
64
     */
65
    public $ipstack_default_fields = array("ip", "country_code", "country_name");
66
67
68
    /**
69
     * Request attribute with IP address as described here:
70
     * http://www.slimframework.com/docs/v3/cookbook/ip-address.html
71
     * 
72
     * @var string
73
     */
74
    public $ip_address_attribute = "ip_address";
75
76
77
    /**
78
     * Request attribute to store the full ipstack information
79
     * 
80
     * @var string
81
     */
82
    public $ipstack_attribute = "ipstack";
83
84
85
    /**
86
     * @var IpstackClientInterface
87
     */
88
    public $ipstack_client;
89
90
91
    /**
92
     * @var integer
93
     */
94
    public $reponse_error_code = 400;
95
96
97
    /**
98
     * @param IpstackClientInterface $ipstack_client       IpstackClient
99
     * @param string                 $ip_address_attribute Optional: Request attribute name with Client IP address
100
     * @param array                  $request_attributes   Optional: Map ipstack fields to request attributes
0 ignored issues
show
Bug introduced by
There is no parameter named $request_attributes. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
101
     * @param LoggerInterface|null   $logger               Optional: PSR-3 Logger
102
     */
103 48
    public function __construct( IpstackClientInterface $ipstack_client, string $ip_address_attribute = null, array $ipstack_attributes = array(), LoggerInterface $logger = null )
104
    {
105 48
        $this->ipstack_client       = $ipstack_client;        
106 48
        $this->ip_address_attribute = $ip_address_attribute;        
107 48
        $this->ipstack_attributes   = $ipstack_attributes;
108
        
109 48
        $this->setLogger( $logger ?: new NullLogger );
110 48
    }
111
112
113
    /**
114
     * PSR-15 "Single pass" pattern
115
     * 
116
     * @param  ServerRequestInterface  $request [description]
117
     * @param  RequestHandlerInterface $handler [description]
118
     * @return ResponseInterface
119
     */
120 40
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
121
    {
122 40
        if (!$this->business( $request)):
123 24
            $this->logger->info("Force Status 400 response");
124 24
            return new GuzzleResponse( $this->reponse_error_code );
125
        endif;
126
127
        // Call $handler, return response
128 16
        return $handler->handle($request);
129
    }
130
131
132
133
    /**
134
     * Slim3-style "Double Pass" pattern
135
     * 
136
     * @param  ServerRequestInterface $request
137
     * @param  ResponseInterface      $response
138
     * @param  callable               $next
139
     *
140
     * @return ResponseInterface
141
     */
142 40
    public function __invoke( ServerRequestInterface $request, ResponseInterface $response, callable $next )
143
    {
144
145 40
        if (!$this->business( $request)):
146 24
            $this->logger->info("Force Status 400 response");
147 24
            return $response->withStatus( $this->reponse_error_code  );
148
        endif;
149
150
        // Call $next middleware, return response
151 16
        return $next($request, $response);
152
    }
153
154
155
156
157 40
    protected function business( ServerRequestInterface $request )
158
    {
159 40
        $client_ip = $this->getClientIp( $request );
160
161 40
        if (!$this->assertClientIp( $client_ip )):
162 24
            return false;
163
        endif;
164
165
        // Ask IpstackClient and store result in Request
166 16
        $ipstack = $this->askIpStack( $client_ip );
167 16
        $request = $request->withAttribute( $this->ipstack_attribute, $ipstack);
168
169
        // Map certain ipstack fields to custom request attributes
170 16
        foreach( $this->ipstack_attributes as $field => $attr_name):
171
            $request = $request->withAttribute($attr_name, $ipstack[ $field ] ?? null );
172
        endforeach;
173
174 16
        return true;
175
    }
176
177
178
179
    /**
180
     * Returns the client's IP, either from request attribute name or REMOTE_ADDR.
181
     * 
182
     * @param  ServerRequestInterface $request The request
183
     * @return string                          Client IP address string
184
     */
185 40
	protected function getClientIp(ServerRequestInterface $request) : string
186
    {
187 40
        if (!empty($this->ip_address_attribute)):
188 20
        	$client_ip = $request->getAttribute( $this->ip_address_attribute );
189 20
        	$log_msg = "Use IP from Request attribute";
190 20
        	$ip_src  = $this->ip_address_attribute;
191
        else:
192 20
	    	$serverParams = $request->getServerParams();
193 20
	    	$client_ip = $serverParams['REMOTE_ADDR'] ?? "";
194
195 20
        	$log_msg = "Use IP from SERVER";
196 20
        	$ip_src  = "REMOTE_ADDR";
197
        endif;
198
199 40
    	$this->logger->debug($log_msg, [
200 40
			'src' => $ip_src,
201 40
			'ip' => $client_ip
202
    	]);        	
203
204 40
        return $client_ip ?: "";
205
    }
206
207
208
209
    /**
210
     * Asks the ipstack API about information for the given IP.
211
     * 
212
     * If something goes wrong, an array with default values
213
     * will be returned.
214
     *
215
     * @param  string $client_ip
216
     * @return array  ipstack response excerpt.
217
     */
218 16
    protected function askIpStack( string $client_ip ) : array
219
    {
220
    	// Prepare result set
221 16
    	$custom_fields  = array_keys( $this->ipstack_attributes );
222 16
    	$fields         = array_merge($custom_fields, $this->ipstack_default_fields);
223
224 16
    	$default_return = array_fill_keys($fields, null);
225 16
    	$default_return['ip'] = $client_ip;
226
227
    	// The business
228
        try {
229 16
            $ipstack = $this->ipstack_client->get( $client_ip, [
0 ignored issues
show
Unused Code introduced by
The call to IpstackClientInterface::get() has too many arguments starting with array('fields' => join('...s), 'language' => 'de').

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
230 16
                "fields"     => join(",", $fields),
231 16
                "language"   => "de"
232
            ]);
233
234
            // Log things. Make sure to log only default fields
235
            // See "$ipstack_default_fields"
236 16
            $this->logger->notice("Success: ipstack response", [
237 16
                'client_ip'     => $ipstack['ip'],
238 16
                'country_code'  => $ipstack['country_code'],
239 16
                'country_name'  => $ipstack['country_name'],
240
            ]);
241
242
            // Merge ipstack response 
243 16
            $result = array_merge($default_return, $ipstack);
244 16
            return $result;
245
246
        }
247
        catch (IpstackExceptionInterface $e) {
248
        	// At least: 
249
            return $default_return;
250
        }
251
252
    }
253
254
255
256
    /**
257
     * Checks wether a given IP address is not empty and valid IPv4 or IP46.
258
     *
259
     * @param  string $client_ip
260
     * @return bool
261
     */
262 40
    protected function assertClientIp( string $client_ip ) : bool
263
    {
264
265 40
        if (!$client_ip) :
266 16
            $this->logger->error("Empty IP given?!");
267 16
            return false;
268
        endif;
269
        
270 24
        if( filter_var($client_ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) :
271 8
            $this->logger->debug("Valid IPv4 address");
272 8
            return true;
273
        
274 16
        elseif( filter_var($client_ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) :
275 8
            $this->logger->debug("Valid IPv6 address");
276 8
            return true;
277
278
        endif;
279
280 8
        $this->logger->warning("Client IP is neither IPv4 nor IPv6");
281 8
        return false;
282
    }
283
284
}
285