1
|
|
|
<?php |
2
|
|
|
namespace Germania\ClientIpLocation; |
3
|
|
|
|
4
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
5
|
|
|
use Psr\Http\Message\ResponseInterface; |
6
|
|
|
use Psr\Http\Server\RequestHandlerInterface; |
7
|
|
|
use Psr\Http\Server\MiddlewareInterface; |
8
|
|
|
|
9
|
|
|
use Psr\Log\LoggerInterface; |
10
|
|
|
use Psr\Log\LogLevel; |
11
|
|
|
|
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Stores the client's location in the Request. |
15
|
|
|
* |
16
|
|
|
* The location is determined using the client IP coming from a request attribute |
17
|
|
|
* and a location factory callable passed to the constructor. |
18
|
|
|
* |
19
|
|
|
* The client location is not special kind of data; |
20
|
|
|
* any kind of location information coming from the location factory |
21
|
|
|
* will be stored in the request attribute. |
22
|
|
|
*/ |
23
|
|
|
class ClientIpLocationMiddleware implements MiddlewareInterface |
24
|
|
|
{ |
25
|
|
|
|
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Callable that determines Location from Client IP address |
29
|
|
|
* |
30
|
|
|
* @var callable |
31
|
|
|
*/ |
32
|
|
|
public $location_factory; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var \Psr\Log\LoggerInterface |
36
|
|
|
*/ |
37
|
|
|
public $logger; |
38
|
|
|
|
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* PSR-3 Loglevel name for errors |
42
|
|
|
* @var string |
43
|
|
|
*/ |
44
|
|
|
protected $error_loglevel = LogLevel::ERROR; |
45
|
|
|
|
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Request attribute where the canonical URI shall be stored in. |
49
|
|
|
* @var string |
50
|
|
|
*/ |
51
|
|
|
public $client_location_attribute_name = "client-location"; |
52
|
|
|
|
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Request attribute where the Client IP is stored in. |
56
|
|
|
* @var string |
57
|
|
|
*/ |
58
|
|
|
public $client_ip_attribute_name = 'client-ip'; |
59
|
|
|
|
60
|
|
|
|
61
|
|
|
|
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @param callable $location_factory Location factory |
65
|
|
|
* @param LoggerInterface $logger PSR-3 Logger for errors |
66
|
|
|
* @param LogLevel $error_loglevel Optional: PSR-3 Loglevel name for errors (defaults to `error`) |
67
|
|
|
*/ |
68
|
6 |
|
public function __construct( callable $location_factory, LoggerInterface $logger, string $error_loglevel = LogLevel::ERROR) |
69
|
|
|
{ |
70
|
6 |
|
$this->setLocationFactory( $location_factory ); |
71
|
6 |
|
$this->error_loglevel = $error_loglevel; |
|
|
|
|
72
|
6 |
|
$this->logger = $logger; |
73
|
6 |
|
} |
74
|
|
|
|
75
|
|
|
|
76
|
|
|
public function setLocationFactory( callable $location_factory ) : self |
77
|
|
|
{ |
78
|
|
|
$this->location_factory = $location_factory; |
79
|
|
|
return $this; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
|
83
|
|
|
|
84
|
|
|
/** |
85
|
4 |
|
* Single-pass (PSR-15 style) |
86
|
|
|
* |
87
|
|
|
* @param ServerRequestInterface $request |
88
|
4 |
|
* @param RequestHandlerInterface $handler |
89
|
|
|
* |
90
|
4 |
|
* @return ResponseInterface |
91
|
2 |
|
*/ |
92
|
|
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface |
93
|
|
|
{ |
94
|
|
|
|
95
|
|
|
$client_ip = $request->getAttribute( $this->client_ip_attribute_name ) ?: null; |
96
|
2 |
|
|
97
|
2 |
|
if (empty($client_ip)) { |
98
|
|
|
return $handler->handle($request); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
|
102
|
|
|
try { |
103
|
|
|
$location = ($this->location_factory)( $client_ip ); |
104
|
|
|
$request = $request->withAttribute($this->client_location_attribute_name, $location); |
105
|
|
|
} |
106
|
|
|
catch (\Throwable $e) { |
107
|
|
|
$msg = sprintf("ClientIpLocationMiddleware: %s", $e->getMessage()); |
108
|
|
|
$msg_location = sprintf("%s:%s", $e->getFile(), $e->getLine()); |
109
|
|
|
$this->logger->log( $this->error_loglevel, $msg, [ |
110
|
2 |
|
'type' => get_class($e), |
111
|
|
|
'code' => $e->getCode(), |
112
|
|
|
'location' => $msg_location, |
113
|
|
|
'clientIp' => $client_ip |
114
|
|
|
]); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
|
118
|
6 |
|
return $handler->handle($request); |
119
|
|
|
} |
120
|
6 |
|
|
121
|
6 |
|
|
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* @param string $attr_name Client IP Attribute Name |
125
|
|
|
*/ |
126
|
|
|
public function setClientIpAttributeName( string $attr_name ) : self |
127
|
|
|
{ |
128
|
|
|
$this->client_ip_attribute_name = $attr_name; |
129
|
6 |
|
return $this; |
130
|
|
|
} |
131
|
6 |
|
|
132
|
6 |
|
|
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* @param string $attr_name Client Location Attribute Name |
136
|
|
|
*/ |
137
|
|
|
public function setClientLocationAttributeName( string $attr_name ) : self |
138
|
|
|
{ |
139
|
|
|
$this->client_location_attribute_name = $attr_name; |
140
|
|
|
return $this; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
|
144
|
|
|
} |
145
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.