├── .github
└── workflows
│ └── main.yaml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── composer.json
├── phpcs.xml
├── phpunit.xml
└── src
└── Firewall.php
/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | name: "testing"
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | qa:
11 | name: Quality assurance
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v4
17 |
18 | - name: Validate composer.json and composer.lock
19 | run: composer validate
20 |
21 | - name: Cache Composer packages
22 | id: composer-cache
23 | uses: actions/cache@v4
24 | with:
25 | path: vendor
26 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
27 | restore-keys: |
28 | ${{ runner.os }}-php-
29 |
30 | - name: Install dependencies
31 | if: steps.composer-cache.outputs.cache-hit != 'true'
32 | run: composer install --prefer-dist --no-progress
33 |
34 | - name: Coding Standard
35 | run: composer run-script cs
36 |
37 | tests:
38 | name: Tests
39 | runs-on: ubuntu-latest
40 |
41 | strategy:
42 | matrix:
43 | php:
44 | - 7.2
45 | - 7.3
46 | - 7.4
47 | - 8.0
48 | - 8.1
49 | - 8.2
50 | - 8.3
51 | - 8.4
52 |
53 | steps:
54 | - name: Checkout
55 | uses: actions/checkout@v4
56 |
57 | - name: Install PHP
58 | uses: shivammathur/setup-php@v2
59 | with:
60 | php-version: ${{ matrix.php }}
61 |
62 | - name: Cache PHP dependencies
63 | uses: actions/cache@v4
64 | with:
65 | path: vendor
66 | key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
67 | restore-keys: ${{ runner.os }}-php-${{ matrix.php }}-composer-
68 |
69 | - name: Install dependencies
70 | run: composer install --prefer-dist --no-progress
71 |
72 | - name: Tests
73 | run: composer test
74 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/)
5 | and this project adheres to [Semantic Versioning](http://semver.org/).
6 |
7 | ## [2.1.0] - 2025-03-21
8 | ### Added
9 | - Better support for types.
10 |
11 | ## [2.0.4] - 2025-01-16
12 | ### Fixed
13 | - Support for Php 8.4 [#17].
14 |
15 | ## [2.0.3] - 2022-10-04
16 | ### Changed
17 | - Replace abandoned `m6web/firewall` with `mlocati/ip-lib` [#5].
18 |
19 | ### Fixed
20 | - XML configuration for phpunit [#6].
21 | - Added dependabot [#7].
22 |
23 | ## [2.0.2] - 2020-12-02
24 | ### Added
25 | - Support for PHP 8
26 |
27 | ## [2.0.1] - 2020-08-02
28 | ### Fixed
29 | - Fixes a bug that if only backlist is supplied incorrectly getting 403 error [#1]
30 |
31 | ## [2.0.0] - 2019-12-04
32 | ### Added
33 | - New constructor argument to set a `ResponseFactoryInterface`
34 |
35 | ### Removed
36 | - Support for PHP 7.0 and 7.1
37 | - The option `responseFactory`. Use the constructor argument.
38 |
39 | ## [1.1.0] - 2018-08-04
40 | ### Added
41 | - PSR-17 support
42 | - New option `responseFactory`
43 |
44 | ## [1.0.0] - 2018-01-27
45 | ### Added
46 | - Improved testing and added code coverage reporting
47 | - Added tests for PHP 7.2
48 |
49 | ### Changed
50 | - Upgraded to the final version of PSR-15 `psr/http-server-middleware`
51 |
52 | ### Fixed
53 | - Updated license year
54 |
55 | ## [0.5.0] - 2017-11-13
56 | ### Changed
57 | - Replaced `http-interop/http-middleware` with `http-interop/http-server-middleware`.
58 |
59 | ### Removed
60 | - Removed support for PHP 5.x.
61 |
62 | ## [0.4.0] - 2017-09-21
63 | ### Changed
64 | - Append `.dist` suffix to phpcs.xml and phpunit.xml files
65 | - Changed the configuration of phpcs and php_cs
66 | - Upgraded phpunit to the latest version and improved its config file
67 | - Updated to `http-interop/http-middleware#0.5`
68 |
69 | ## [0.3.0] - 2016-12-26
70 | ### Changed
71 | - Updated tests
72 | - Updated to `http-interop/http-middleware#0.4`
73 | - Updated `friendsofphp/php-cs-fixer#2.0`
74 |
75 | ## [0.2.0] - 2016-11-27
76 | ### Changed
77 | - Updated to `http-interop/http-middleware#0.3`
78 |
79 | ## [0.1.0] - 2016-10-10
80 | First version
81 |
82 | [#1]: https://github.com/middlewares/firewall/issues/1
83 | [#5]: https://github.com/middlewares/firewall/issues/5
84 | [#6]: https://github.com/middlewares/firewall/issues/6
85 | [#7]: https://github.com/middlewares/firewall/issues/7
86 | [#17]: https://github.com/middlewares/firewall/issues/17
87 |
88 | [2.1.0]: https://github.com/middlewares/firewall/compare/v2.0.4...v2.1.0
89 | [2.0.4]: https://github.com/middlewares/firewall/compare/v2.0.3...v2.0.4
90 | [2.0.3]: https://github.com/middlewares/firewall/compare/v2.0.2...v2.0.3
91 | [2.0.2]: https://github.com/middlewares/firewall/compare/v2.0.1...v2.0.2
92 | [2.0.1]: https://github.com/middlewares/firewall/compare/v2.0.0...v2.0.1
93 | [2.0.0]: https://github.com/middlewares/firewall/compare/v1.1.0...v2.0.0
94 | [1.1.0]: https://github.com/middlewares/firewall/compare/v1.0.0...v1.1.0
95 | [1.0.0]: https://github.com/middlewares/firewall/compare/v0.5.0...v1.0.0
96 | [0.5.0]: https://github.com/middlewares/firewall/compare/v0.4.0...v0.5.0
97 | [0.4.0]: https://github.com/middlewares/firewall/compare/v0.3.0...v0.4.0
98 | [0.3.0]: https://github.com/middlewares/firewall/compare/v0.2.0...v0.3.0
99 | [0.2.0]: https://github.com/middlewares/firewall/compare/v0.1.0...v0.2.0
100 | [0.1.0]: https://github.com/middlewares/firewall/releases/tag/v0.1.0
101 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guide
2 |
3 | This project adheres to [The Code Manifesto](http://codemanifesto.com) as its guidelines for contributor interactions.
4 |
5 | ## The Code Manifesto
6 |
7 | We want to work in an ecosystem that empowers developers to reach their potential--one that encourages growth and effective collaboration. A space that is safe for all.
8 |
9 | A space such as this benefits everyone that participates in it. It encourages new developers to enter our field. It is through discussion and collaboration that we grow, and through growth that we improve.
10 |
11 | In the effort to create such a place, we hold to these values:
12 |
13 | 1. **Discrimination limits us.** This includes discrimination on the basis of race, gender, sexual orientation, gender identity, age, nationality, technology and any other arbitrary exclusion of a group of people.
14 | 2. **Boundaries honor us.** Your comfort levels are not everyone’s comfort levels. Remember that, and if brought to your attention, heed it.
15 | 3. **We are our biggest assets.** None of us were born masters of our trade. Each of us has been helped along the way. Return that favor, when and where you can.
16 | 4. **We are resources for the future.** As an extension of #3, share what you know. Make yourself a resource to help those that come after you.
17 | 5. **Respect defines us.** Treat others as you wish to be treated. Make your discussions, criticisms and debates from a position of respectfulness. Ask yourself, is it true? Is it necessary? Is it constructive? Anything less is unacceptable.
18 | 6. **Reactions require grace.** Angry responses are valid, but abusive language and vindictive actions are toxic. When something happens that offends you, handle it assertively, but be respectful. Escalate reasonably, and try to allow the offender an opportunity to explain themselves, and possibly correct the issue.
19 | 7. **Opinions are just that: opinions.** Each and every one of us, due to our background and upbringing, have varying opinions. That is perfectly acceptable. Remember this: if you respect your own opinions, you should respect the opinions of others.
20 | 8. **To err is human.** You might not intend it, but mistakes do happen and contribute to build experience. Tolerate honest mistakes, and don't hesitate to apologize if you make one yourself.
21 |
22 | ## How to contribute
23 |
24 | This is a collaborative effort. We welcome all contributions submitted as pull requests.
25 |
26 | (Contributions on wording & style are also welcome.)
27 |
28 | ### Bugs
29 |
30 | A bug is a demonstrable problem that is caused by the code in the repository. Good bug reports are extremely helpful – thank you!
31 |
32 | Please try to be as detailed as possible in your report. Include specific information about the environment – version of PHP, etc, and steps required to reproduce the issue.
33 |
34 | ### Pull Requests
35 |
36 | Good pull requests – patches, improvements, new features – are a fantastic help. Before create a pull request, please follow these instructions:
37 |
38 | * The code must follow the [PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). Run `composer cs-fix` to fix your code before commit.
39 | * Write tests
40 | * Document any change in `README.md` and `CHANGELOG.md`
41 | * One pull request per feature. If you want to do more than one thing, send multiple pull request
42 |
43 | ### Runing tests
44 |
45 | ```sh
46 | composer test
47 | ```
48 |
49 | To get code coverage information execute the following comand:
50 |
51 | ```sh
52 | composer coverage
53 | ```
54 |
55 | Then, open the `./coverage/index.html` file in your browser.
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019-2025
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # middlewares/firewall
2 |
3 | [![Latest Version on Packagist][ico-version]][link-packagist]
4 | [![Software License][ico-license]](LICENSE)
5 | ![Testing][ico-ga]
6 | [![Total Downloads][ico-downloads]][link-downloads]
7 |
8 | Middleware to provide IP filtering.
9 |
10 | ## Requirements
11 |
12 | * PHP >= 7.2
13 | * A [PSR-7 http library](https://github.com/middlewares/awesome-psr15-middlewares#psr-7-implementations)
14 | * A [PSR-15 middleware dispatcher](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher)
15 |
16 | ## Installation
17 |
18 | This package is installable and autoloadable via Composer as [middlewares/firewall](https://packagist.org/packages/middlewares/firewall).
19 |
20 | ```sh
21 | composer require middlewares/firewall
22 | ```
23 |
24 | ## Example
25 |
26 | ```php
27 | Dispatcher::run([
28 | (new Middlewares\Firewall(['123.0.0.*']))
29 | ->blacklist([
30 | '123.0.0.1',
31 | '123.0.0.2',
32 | ])
33 | ]);
34 | ```
35 |
36 | ## Usage
37 |
38 | The constructor accepts an array with the whitelist ips. [See the ip formats allowed](https://github.com/M6Web/Firewall#entries-formats).
39 |
40 | ```php
41 | $firewall = new Middlewares\Firewall([
42 | '127.0.0.1',
43 | '198.168.0.*',
44 | ]);
45 | ```
46 |
47 | Optionally, you can provide a `Psr\Http\Message\ResponseFactoryInterface` as the second argument to create the error response (`403`). If it's not defined, [Middleware\Utils\Factory](https://github.com/middlewares/utils#factory) will be used to detect it automatically.
48 |
49 | ```php
50 | $responseFactory = new MyOwnResponseFactory();
51 |
52 | $firewall = new Middlewares\Firewall($whitelist, $responseFactory);
53 | ```
54 |
55 | ### blacklist
56 |
57 | The blacklist ips. The ip format is the same than whitelist.
58 |
59 | ```php
60 | $whitelist = [
61 | '127.0.0.1',
62 | '198.168.0.*',
63 | ];
64 | $blacklist = [
65 | '192.168.0.50',
66 | ];
67 |
68 | $firewall = (new Middlewares\Firewall($whitelist))->blacklist($blacklist);
69 | ```
70 |
71 | ### ipAttribute
72 |
73 | By default uses the `REMOTE_ADDR` server parameter to get the client ip. Use this option if you want to use a request attribute. Useful to combine with any ip detection middleware, for example [client-ip](https://github.com/middlewares/client-ip):
74 |
75 | ```php
76 | Dispatcher::run([
77 | //detect the client ip and save it in client-ip attribute
78 | new Middlewares\ClientIP(),
79 |
80 | //use that attribute
81 | (new Middlewares\Firewall(['123.0.0.*']))
82 | ->ipAttribute('client-ip')
83 | ]);
84 | ```
85 |
86 | ---
87 |
88 | Please see [CHANGELOG](CHANGELOG.md) for more information about recent changes and [CONTRIBUTING](CONTRIBUTING.md) for contributing details.
89 |
90 | The MIT License (MIT). Please see [LICENSE](LICENSE) for more information.
91 |
92 | [ico-version]: https://img.shields.io/packagist/v/middlewares/firewall.svg?style=flat-square
93 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
94 | [ico-ga]: https://github.com/middlewares/firewall/workflows/testing/badge.svg
95 | [ico-downloads]: https://img.shields.io/packagist/dt/middlewares/firewall.svg?style=flat-square
96 |
97 | [link-packagist]: https://packagist.org/packages/middlewares/firewall
98 | [link-downloads]: https://packagist.org/packages/middlewares/firewall
99 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "middlewares/firewall",
3 | "type": "library",
4 | "description": "Middleware to provide IP filtering",
5 | "license": "MIT",
6 | "keywords": [
7 | "psr-7",
8 | "psr-15",
9 | "middleware",
10 | "server",
11 | "http",
12 | "firewall",
13 | "security",
14 | "ip",
15 | "filter"
16 | ],
17 | "homepage": "https://github.com/middlewares/firewall",
18 | "support": {
19 | "issues": "https://github.com/middlewares/firewall/issues"
20 | },
21 | "require": {
22 | "php": "^7.2 || ^8.0",
23 | "middlewares/utils": "^2 || ^3 || ^4",
24 | "psr/http-server-middleware": "^1",
25 | "mlocati/ip-lib": "^1.18"
26 | },
27 | "require-dev": {
28 | "phpunit/phpunit": "^8 || ^9",
29 | "friendsofphp/php-cs-fixer": "^3",
30 | "squizlabs/php_codesniffer": "^3",
31 | "oscarotero/php-cs-fixer-config": "^2",
32 | "laminas/laminas-diactoros": "^2 || ^3",
33 | "phpstan/phpstan": "^1 || ^2"
34 | },
35 | "autoload": {
36 | "psr-4": {
37 | "Middlewares\\": "src/"
38 | }
39 | },
40 | "autoload-dev": {
41 | "psr-4": {
42 | "Middlewares\\Tests\\": "tests/"
43 | }
44 | },
45 | "scripts": {
46 | "cs": "phpcs",
47 | "cs-fix": "php-cs-fixer fix",
48 | "phpstan": "phpstan analyse",
49 | "test": "phpunit",
50 | "coverage": "phpunit --coverage-text",
51 | "coverage-html": "phpunit --coverage-html=coverage"
52 | }
53 | }
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Middlewares coding standard
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | src
15 | tests
16 |
17 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | tests
6 |
7 |
8 |
9 |
10 | ./src
11 |
12 | ./tests
13 | ./vendor
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/Firewall.php:
--------------------------------------------------------------------------------
1 | whitelist = $whitelist ?? [];
37 | $this->responseFactory = $responseFactory ?: Factory::getResponseFactory();
38 | }
39 |
40 | /**
41 | * Set ips not allowed.
42 | *
43 | * @param string[] $blacklist
44 | */
45 | public function blacklist(array $blacklist): self
46 | {
47 | $this->blacklist = $blacklist;
48 |
49 | return $this;
50 | }
51 |
52 | /**
53 | * Set the attribute name to get the client ip.
54 | */
55 | public function ipAttribute(string $ipAttribute): self
56 | {
57 | $this->ipAttribute = $ipAttribute;
58 |
59 | return $this;
60 | }
61 |
62 | /**
63 | * Process a server request and return a response.
64 | */
65 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
66 | {
67 | $ip = $this->getIp($request);
68 |
69 | if (empty($ip)) {
70 | return $this->responseFactory->createResponse(403);
71 | }
72 |
73 | if (!$this->isIpAccessible($ip)) {
74 | return $this->responseFactory->createResponse(403);
75 | }
76 |
77 | return $handler->handle($request);
78 | }
79 |
80 | /**
81 | * Get the client ip.
82 | */
83 | private function getIp(ServerRequestInterface $request): string
84 | {
85 | $server = $request->getServerParams();
86 |
87 | if ($this->ipAttribute !== null) {
88 | return $request->getAttribute($this->ipAttribute);
89 | }
90 |
91 | return isset($server['REMOTE_ADDR']) ? $server['REMOTE_ADDR'] : '';
92 | }
93 |
94 | /**
95 | * Create range class instance from string
96 | */
97 | protected function createRangeInstance(string $range): RangeInterface
98 | {
99 | if (strpos($range, '-') !== false) {
100 | $parts = explode('-', $range, 2);
101 |
102 | /* @phpstan-ignore-next-line */
103 | return IPFactory::getRangesFromBoundaries($parts[0], $parts[1]);
104 | }
105 |
106 | /* @phpstan-ignore-next-line */
107 | return IPFactory::parseRangeString($range);
108 | }
109 |
110 | /**
111 | * Convert IP list to range array
112 | *
113 | * @param string[]|null $list Data that needs to be converted
114 | *
115 | * @return array
116 | */
117 | private function convertListToRangeArray(?array $list): array
118 | {
119 | if ($list === null) {
120 | return [];
121 | }
122 |
123 | return array_map(
124 | [$this, 'createRangeInstance'],
125 | $list
126 | );
127 | }
128 |
129 | /**
130 | * Checks if IP address is in list
131 | *
132 | * @param AddressInterface $address IP address to check
133 | * @param string[] $list List of addresses to check
134 | *
135 | * @return bool
136 | */
137 | private function isAddressInList(AddressInterface $address, array $list): bool
138 | {
139 | foreach ($this->convertListToRangeArray($list) as $ipRange) {
140 | if ($ipRange->contains($address)) {
141 | return true;
142 | }
143 | }
144 |
145 | return false;
146 | }
147 |
148 | /**
149 | * Checks if IP by current addresses is accessible
150 | *
151 | * @param string $ip Current IP
152 | *
153 | * @return bool
154 | */
155 | private function isIpAccessible(string $ip): bool
156 | {
157 | if (!count($this->blacklist) && !count($this->whitelist)) {
158 | return true;
159 | }
160 |
161 | $address = IPFactory::parseAddressString($ip);
162 | if ($address === null) {
163 | return false;
164 | }
165 |
166 | if (count($this->blacklist) > 0 && !count($this->whitelist)) {
167 | return $this->isAddressInList($address, $this->whitelist);
168 | }
169 |
170 | if (count($this->whitelist) > 0 && !count($this->blacklist)) {
171 | return !$this->isAddressInList($address, $this->blacklist);
172 | }
173 |
174 | return $this->isAddressInList($address, $this->whitelist) &&
175 | !$this->isAddressInList($address, $this->blacklist);
176 | }
177 | }
178 |
--------------------------------------------------------------------------------