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.
Passed
Push — master ( a60f7c...123b88 )
by Carsten
11:33 queued 12s
created

IpstackMiddleware::__invoke()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18

Duplication

Lines 3
Ratio 16.67 %

Code Coverage

Tests 8
CRAP Score 3.0123

Importance

Changes 0
Metric Value
dl 3
loc 18
ccs 8
cts 9
cp 0.8889
rs 9.6666
c 0
b 0
f 0
cc 3
nc 3
nop 3
crap 3.0123
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 View Code Duplication
    public function __construct( IpstackClientInterface $ipstack_client, string $ip_address_attribute = null, array $ipstack_attributes = array(), LoggerInterface $logger = null )
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
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
        $ipstack = $this->business( $request);
123 40
        if (false === $ipstack):
124 24
            $this->logger->info("Force Status 400 response");
125 24
            return new GuzzleResponse( $this->reponse_error_code );
126
        endif;
127
128 16
        $request = $request->withAttribute( $this->ipstack_attribute, $ipstack);
129
        // Map certain ipstack fields to custom request attributes
130 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...
131
            $request = $request->withAttribute($attr_name, $ipstack[ $field ] ?? null );
132
        endforeach;
133
134
        // Call $handler, return response
135 16
        return $handler->handle($request);
136
    }
137
138
139
140
    /**
141
     * Slim3-style "Double Pass" pattern
142
     * 
143
     * @param  ServerRequestInterface $request
144
     * @param  ResponseInterface      $response
145
     * @param  callable               $next
146
     *
147
     * @return ResponseInterface
148
     */
149 40
    public function __invoke( ServerRequestInterface $request, ResponseInterface $response, callable $next )
150
    {
151 40
        $ipstack = $this->business( $request);
152 40
        if (false === $ipstack):
153 24
            $this->logger->info("Force Status 400 response");
154 24
            return $response->withStatus( $this->reponse_error_code  );
155
        endif;
156
157 16
        $request = $request->withAttribute( $this->ipstack_attribute, $ipstack);
158
        // Map certain ipstack fields to custom request attributes
159 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...
160
            $request = $request->withAttribute($attr_name, $ipstack[ $field ] ?? null );
161
        endforeach;
162
163
164
        // Call $next middleware, return response
165 16
        return $next($request, $response);
166
    }
167
168
169
170
    /**
171
     * Perfoms the middelware action
172
     * 
173
     * @param  ServerRequestInterface $request
174
     * @return bool
175
     */
176 40
    protected function business( ServerRequestInterface $request )
177
    {
178 40
        $client_ip = $this->getClientIp( $request );
179
180 40
        if (!$this->assertClientIp( $client_ip )):
181 24
            return false;
182
        endif;
183
184
        // Ask IpstackClient and store result in Request
185 16
        $ipstack = $this->askIpStack( $client_ip );
186
187 16
        return $ipstack;
188
    }
189
190
191
192
    /**
193
     * Returns the client's IP, either from request attribute name or REMOTE_ADDR.
194
     * 
195
     * @param  ServerRequestInterface $request The request
196
     * @return string                          Client IP address string
197
     */
198 40
    protected function getClientIp(ServerRequestInterface $request) : string
199
    {
200 40
        if (!empty($this->ip_address_attribute)):
201 20
            $client_ip = $request->getAttribute( $this->ip_address_attribute );
202 20
            $log_msg = "Use IP from Request attribute";
203 20
            $ip_src  = $this->ip_address_attribute;
204
        else:
205 20
            $serverParams = $request->getServerParams();
206 20
            $client_ip = $serverParams['REMOTE_ADDR'] ?? "";
207
208 20
            $log_msg = "Use IP from SERVER";
209 20
            $ip_src  = "REMOTE_ADDR";
210
        endif;
211
212 40
        $this->logger->debug($log_msg, [
213 40
            'src' => $ip_src,
214 40
            'ip' => $client_ip
215
        ]);         
216
217 40
        return $client_ip ?: "";
218
    }
219
220
221
222
    /**
223
     * Asks the ipstack API about information for the given IP.
224
     * 
225
     * If something goes wrong, an array with default values
226
     * will be returned.
227
     *
228
     * @param  string $client_ip
229
     * @return array  ipstack response excerpt.
230
     */
231 16
    protected function askIpStack( string $client_ip ) : array
232
    {
233
        // Prepare result set
234 16
        $custom_fields  = array_keys( $this->ipstack_attributes );
235 16
        $fields         = array_merge($custom_fields, $this->ipstack_default_fields);
236
237 16
        $default_return = array_fill_keys($fields, null);
238 16
        $default_return['ip'] = $client_ip;
239
240
        // The business
241
        try {
242 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...
243 16
                "fields"     => join(",", $fields),
244 16
                "language"   => "de"
245
            ]);
246
247
            // Log things. Make sure to log only default fields
248
            // See "$ipstack_default_fields"
249 16
            $this->logger->notice("Success: ipstack response", [
250 16
                'client_ip'     => $ipstack['ip'],
251 16
                'country_code'  => $ipstack['country_code'],
252 16
                'country_name'  => $ipstack['country_name'],
253
            ]);
254
255
            // Merge ipstack response 
256 16
            $result = array_merge($default_return, $ipstack);
257 16
            return $result;
258
259
        }
260
        catch (IpstackExceptionInterface $e) {
261
            // At least: 
262
            return $default_return;
263
        }
264
265
    }
266
267
268
269
    /**
270
     * Checks wether a given IP address is not empty and valid IPv4 or IP46.
271
     *
272
     * @param  string $client_ip
273
     * @return bool
274
     */
275 40
    protected function assertClientIp( string $client_ip ) : bool
276
    {
277
278 40
        if (!$client_ip) :
279 16
            $this->logger->error("Empty IP given?!");
280 16
            return false;
281
        endif;
282
        
283 24
        if( filter_var($client_ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) :
284 8
            $this->logger->debug("Valid IPv4 address");
285 8
            return true;
286
        
287 16
        elseif( filter_var($client_ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) :
288 8
            $this->logger->debug("Valid IPv6 address");
289 8
            return true;
290
291
        endif;
292
293 8
        $this->logger->warning("Client IP is neither IPv4 nor IPv6");
294 8
        return false;
295
    }
296
297
}
298