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.

IpstackMiddleware::__invoke()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 19

Duplication

Lines 8
Ratio 42.11 %

Code Coverage

Tests 9
CRAP Score 3.009

Importance

Changes 0
Metric Value
dl 8
loc 19
ccs 9
cts 10
cp 0.9
rs 9.6333
c 0
b 0
f 0
cc 3
nc 3
nop 3
crap 3.009
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
    /**
99
     * @var string
100
     */
101
    public $success_loglevel = "notice";
102
103
104
    /**
105
     * @var string
106
     */
107
    public $invalid_ip_loglevel = "error";
108
109
110
    /**
111
     * @var string
112
     */
113
    public $ipstack_error_loglevel = "error";
114
115
116
    /**
117
     * @param IpstackClientInterface $ipstack_client       IpstackClient
118
     * @param string                 $ip_address_attribute Optional: Request attribute name with Client IP address
119
     * @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...
120
     * @param LoggerInterface|null   $logger               Optional: PSR-3 Logger
121
     */
122 48
    public function __construct( IpstackClientInterface $ipstack_client, string $ip_address_attribute = null, array $ipstack_attributes = array(), LoggerInterface $logger = null, string $success_loglevel = null , string $invalid_ip_loglevel = null , string $ipstack_error_loglevel = null )
123
    {
124 48
        $this->ipstack_client         = $ipstack_client;
125 48
        $this->ip_address_attribute   = $ip_address_attribute;
126 48
        $this->ipstack_attributes     = $ipstack_attributes;
127 48
        $this->success_loglevel       = $success_loglevel ?: $this->success_loglevel;
128 48
        $this->invalid_ip_loglevel    = $invalid_ip_loglevel ?: $this->invalid_ip_loglevel;
129 48
        $this->ipstack_error_loglevel = $ipstack_error_loglevel ?: $this->ipstack_error_loglevel;
130
131 48
        $this->setLogger( $logger ?: new NullLogger );
132 48
    }
133
134
135
    /**
136
     * PSR-15 "Single pass" pattern
137
     *
138
     * @param  ServerRequestInterface  $request [description]
139
     * @param  RequestHandlerInterface $handler [description]
140
     * @return ResponseInterface
141
     */
142 40
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
143
    {
144 40
        $ipstack = $this->business( $request);
145 40 View Code Duplication
        if (false === $ipstack):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146 24
            $m = sprintf("Could not determine client IP, force status '%s' response", $this->reponse_error_code);
147 24
            $this->logger->log($this->invalid_ip_loglevel, $m);
148 24
            return new GuzzleResponse( $this->reponse_error_code );
149
        endif;
150
151 16
        $request = $request->withAttribute( $this->ipstack_attribute, $ipstack);
152
        // Map certain ipstack fields to custom request attributes
153 16 View Code Duplication
        foreach( $this->ipstack_attributes as $field => $attr_name):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
154
            $request = $request->withAttribute($attr_name, $ipstack[ $field ] ?? null );
155
        endforeach;
156
157
        // Call $handler, return response
158 16
        return $handler->handle($request);
159
    }
160
161
162
163
    /**
164
     * Slim3-style "Double Pass" pattern
165
     *
166
     * @param  ServerRequestInterface $request
167
     * @param  ResponseInterface      $response
168
     * @param  callable               $next
169
     *
170
     * @return ResponseInterface
171
     */
172 40
    public function __invoke( ServerRequestInterface $request, ResponseInterface $response, callable $next )
173
    {
174 40
        $ipstack = $this->business( $request);
175 40 View Code Duplication
        if (false === $ipstack):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176 24
            $m = sprintf("Could not determine client IP, force status '%s' response", $this->reponse_error_code);
177 24
            $this->logger->log($this->invalid_ip_loglevel, $m);
178 24
            return $response->withStatus( $this->reponse_error_code  );
179
        endif;
180
181 16
        $request = $request->withAttribute( $this->ipstack_attribute, $ipstack);
182
        // Map certain ipstack fields to custom request attributes
183 16 View Code Duplication
        foreach( $this->ipstack_attributes as $field => $attr_name):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
            $request = $request->withAttribute($attr_name, $ipstack[ $field ] ?? null );
185
        endforeach;
186
187
188
        // Call $next middleware, return response
189 16
        return $next($request, $response);
190
    }
191
192
193
194
    /**
195
     * Perfoms the middelware action
196
     *
197
     * @param  ServerRequestInterface $request
198
     * @return bool
199
     */
200 40
    protected function business( ServerRequestInterface $request )
201
    {
202 40
        $client_ip = $this->getClientIp( $request );
203
204 40
        if (!$this->assertClientIp( $client_ip )):
205 24
            return false;
206
        endif;
207
208
        // Ask IpstackClient and store result in Request
209 16
        $ipstack = $this->askIpStack( $client_ip );
210
211 16
        return $ipstack;
212
    }
213
214
215
216
    /**
217
     * Returns the client's IP, either from request attribute name or REMOTE_ADDR.
218
     *
219
     * @param  ServerRequestInterface $request The request
220
     * @return string                          Client IP address string
221
     */
222 40
    protected function getClientIp(ServerRequestInterface $request) : string
223
    {
224 40
        if (!empty($this->ip_address_attribute)):
225 20
            $client_ip = $request->getAttribute( $this->ip_address_attribute );
226 20
            $log_msg = "Use IP from Request attribute";
227 20
            $ip_src  = $this->ip_address_attribute;
228
        else:
229 20
            $serverParams = $request->getServerParams();
230 20
            $client_ip = $serverParams['REMOTE_ADDR'] ?? "";
231
232 20
            $log_msg = "Use IP from SERVER";
233 20
            $ip_src  = "REMOTE_ADDR";
234
        endif;
235
236 40
        $this->logger->debug($log_msg, [
237 40
            'src' => $ip_src,
238 40
            'clientIp' => $client_ip
239
        ]);
240
241 40
        return $client_ip ?: "";
242
    }
243
244
245
246
    /**
247
     * Asks the ipstack API about information for the given IP.
248
     *
249
     * If something goes wrong, an array with default values
250
     * will be returned.
251
     *
252
     * @param  string $client_ip
253
     * @return array  ipstack response excerpt.
254
     */
255 16
    protected function askIpStack( string $client_ip ) : array
256
    {
257
        // Prepare result set
258 16
        $custom_fields  = array_keys( $this->ipstack_attributes );
259 16
        $fields         = array_merge($custom_fields, $this->ipstack_default_fields);
260
261 16
        $default_return = array_fill_keys($fields, null);
262 16
        $default_return['ip'] = $client_ip;
263
264
        // The business
265
        try {
266 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...
267 16
                "fields"     => join(",", $fields),
268 16
                "language"   => "de"
269
            ]);
270
271
            // Log things. Make sure to log only default fields
272
            // See "$ipstack_default_fields"
273 16
            $this->logger->log($this->success_loglevel, "Success: ipstack response", [
274 16
                'clientIp'     => $ipstack['ip'],
275 16
                'countryCode'  => $ipstack['country_code'],
276 16
                'countryName'  => $ipstack['country_name'],
277
            ]);
278
279
            // Merge ipstack response
280 16
            $result = array_merge($default_return, $ipstack);
281 16
            return $result;
282
283
        }
284
        catch (IpstackExceptionInterface $e) {
285
            $this->logger->log($this->ipstack_error_loglevel, "Asking ipstack failed", [
286
                'clientIp' => $client_ip,
287
                'exceptionClass' => get_class($e),
288
                'exceptionMessage' => $e->getMessage()
289
            ]);
290
291
            // At least:
292
            return $default_return;
293
        }
294
295
    }
296
297
298
299
    /**
300
     * Checks wether a given IP address is not empty and valid IPv4 or IP46.
301
     *
302
     * @param  string $client_ip
303
     * @return bool
304
     */
305 40
    protected function assertClientIp( string $client_ip ) : bool
306
    {
307
308 40
        if (!$client_ip) :
309 16
            $this->logger->error("Empty IP given?!");
310 16
            return false;
311
        endif;
312
313 24
        if( filter_var($client_ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) :
314 8
            $this->logger->debug("Valid IPv4 address");
315 8
            return true;
316
317 16
        elseif( filter_var($client_ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) :
318 8
            $this->logger->debug("Valid IPv6 address");
319 8
            return true;
320
321
        endif;
322
323 8
        $this->logger->warning("Client IP is neither IPv4 nor IPv6");
324 8
        return false;
325
    }
326
327
}
328