Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

国コード指定でアクセスブロック機能 #291 #362

Merged
merged 7 commits into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/app/temp/debug_html/*
/app/temp/log/*
/app/temp/installed.lock
/app/temp/GeoLite2-Country.mmdb
/app/temp/github_release_cache.json
/app/version
/app/vendor/
Expand Down
4 changes: 4 additions & 0 deletions app/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
// ログイン時にメール認証を有効化するか
define("MFA_EMAIL", "0");

// 国コード(ISO Code)指定でアクセスブロック
//define("USER_BLOCK_COUNTRY_ISO_CODE_CSV", "JP");
//define("ADMIN_BLOCK_COUNTRY_ISO_CODE_CSV", "JP,US");

// If you want get error log on display.
// define('ERROR_ON_DISPLAY', "1");
// ini_set('display_errors', '1');
Expand Down
4 changes: 4 additions & 0 deletions app/config_read_from_env.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
define("EMERGENCY_PASSWORD_RESET_ENABLE", (string)getenv("FC2_EMERGENCY_PASSWORD_RESET_ENABLE"));
define("MFA_EMAIL", (string)getenv("FC2_MFA_EMAIL"));

// 国コード(ISO Code)指定でアクセスブロック
define("USER_BLOCK_COUNTRY_ISO_CODE_CSV", (string)getenv("FC2_USER_BLOCK_COUNTRY_ISO_CODE_CSV"));
define("ADMIN_BLOCK_COUNTRY_ISO_CODE_CSV", (string)getenv("FC2_ADMIN_BLOCK_COUNTRY_ISO_CODE_CSV"));

if (strlen((string)getenv("FC2_GITHUB_REPO")) > 0) {
define("GITHUB_REPO", (string)getenv("FC2_GITHUB_REPO"));
}
96 changes: 96 additions & 0 deletions app/src/Service/AccessBlock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);

namespace Fc2blog\Service;

use Exception;
use Fc2blog\App;
use Fc2blog\Web\Request;
use MaxMind\Db\Reader;

class AccessBlock
{
const MMDB_FILE_PATH = App::TEMP_DIR . "/GeoLite2-Country.mmdb";

private $user_block_country_iso_code_csv;
private $admin_block_country_iso_code_csv;

public function __construct(
string $user_block_country_iso_code_csv = "",
string $admin_block_country_iso_code_csv = ""
)
{
if (strlen($user_block_country_iso_code_csv) > 0) {
$this->user_block_country_iso_code_csv = $user_block_country_iso_code_csv;
} elseif (defined("USER_BLOCK_COUNTRY_ISO_CODE_CSV")) {
$this->user_block_country_iso_code_csv = USER_BLOCK_COUNTRY_ISO_CODE_CSV;
} else {
$this->user_block_country_iso_code_csv = "";
}

if (strlen($admin_block_country_iso_code_csv) > 0) {
$this->admin_block_country_iso_code_csv = $admin_block_country_iso_code_csv;
} elseif (defined("ADMIN_BLOCK_COUNTRY_ISO_CODE_CSV")) {
$this->admin_block_country_iso_code_csv = ADMIN_BLOCK_COUNTRY_ISO_CODE_CSV;
} else {
$this->admin_block_country_iso_code_csv = "";
}

}

public function isAdminBlockIp(Request $request): bool
{
if (strlen($this->admin_block_country_iso_code_csv) === 0) return false;
/** @noinspection PhpUnhandledExceptionInspection */ // エラーなら、アプリは停止で良い
return $this->isBlockIp($request, $this->admin_block_country_iso_code_csv);
}

public function isUserBlockIp(Request $request): bool
{
if (strlen($this->user_block_country_iso_code_csv) === 0) return false;
/** @noinspection PhpUnhandledExceptionInspection */ // エラーなら、アプリは停止で良い
return $this->isBlockIp($request, $this->user_block_country_iso_code_csv);
}

/**
* Check IP address that have to blocked with Read MaxMind Geo ip database.
* @param Request $request
* @param string $block_country_iso_code_csv
* @return bool
* @throws Reader\InvalidDatabaseException
* @throws Exception
*/
public function isBlockIp(Request $request, string $block_country_iso_code_csv): bool
{
if (
!file_exists(self::MMDB_FILE_PATH) ||
!is_file(self::MMDB_FILE_PATH) ||
!is_readable(self::MMDB_FILE_PATH)
) {
// mmdb file notfound. Not to be checking. Done.
return false;
}

$reader = new Reader(self::MMDB_FILE_PATH);
$result = $reader->get($request->getClientIpAddress());
$reader->close();
if (
!is_array($result) || // If undetermined, Result will be null.
!isset($result['country']) ||
!isset($result['country']['iso_code'])
) {
// Could not detect country information. So allow access.
return false;
}

$determined_country_iso_code = $result['country']['iso_code'];

return $this->isContainCsv($determined_country_iso_code, $block_country_iso_code_csv);
}

private function isContainCsv(string $country_iso_code, string $block_country_iso_code_csv): bool
{
$list = explode(',', $block_country_iso_code_csv);
return in_array($country_iso_code, $list);
}
}
19 changes: 15 additions & 4 deletions app/src/Web/Controller/Admin/AdminController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@
use Fc2blog\App;
use Fc2blog\Model\BlogsModel;
use Fc2blog\Model\UsersModel;
use Fc2blog\Service\AccessBlock;
use Fc2blog\Service\BlogService;
use Fc2blog\Web\Controller\Controller;
use Fc2blog\Web\Request;
use Fc2blog\Web\Session;

abstract class AdminController extends Controller
{
protected function beforeFilter(Request $request)
protected function beforeFilter(Request $request): string
{
// 親のフィルター呼び出し
parent::beforeFilter($request);
$template_path = parent::beforeFilter($request);
if (strlen($template_path) > 0) {
return $template_path;
}

// install.lockファイルがなければインストーラーへ
if (!$this->isInstalled() && (
Expand All @@ -26,6 +30,11 @@ protected function beforeFilter(Request $request)
$this->redirect($request, ['controller' => 'Common', 'action' => 'install']);
}

// IPアドレスからアクセス元の国を推定してのブロック
if ((new AccessBlock())->isAdminBlockIp($request)) {
return $this->error403();
}

if (!$this->isLogin()) {
// 未ログイン時でもアクセス許可するパターンリスト
$allows = array(
Expand All @@ -40,7 +49,7 @@ protected function beforeFilter(Request $request)
if (!isset($allows[$controller_name]) || !in_array($action_name, $allows[$controller_name])) {
$this->redirect($request, array('controller' => 'Session', 'action' => 'login'));
}
return;
return "";
}

if (!$this->isSelectedBlog()) {
Expand All @@ -57,14 +66,16 @@ protected function beforeFilter(Request $request)
$this->setWarnMessage(__('Please select a blog'));
$this->redirect($request, ['controller' => 'Blogs', 'action' => 'index']);
}
return;
return "";
}

// ログイン中でかつブログ選択中の場合ブログ情報を取得し時間設定を行う
$blog = BlogService::getById($this->getBlogIdFromSession());
if (is_array($blog) && isset($blog['timezone'])) {
date_default_timezone_set($blog['timezone']);
}

return "";
}

/**
Expand Down
8 changes: 6 additions & 2 deletions app/src/Web/Controller/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ public function execute($method): void
*/
public function prepare(string $method): string
{
$this->beforeFilter($this->request);
$template_path = $this->beforeFilter($this->request);
if (strlen($template_path) > 0) {
return $template_path;
}

$this->resolvedMethod = $method;

Expand Down Expand Up @@ -130,8 +133,9 @@ protected function isInvalidAjaxRequest(Request $request): bool
return false;
}

protected function beforeFilter(Request $request)
protected function beforeFilter(Request $request): string
{
return "";
}

public function set(string $key, $value)
Expand Down
10 changes: 8 additions & 2 deletions app/src/Web/Controller/User/EntriesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ class EntriesController extends UserController
/**
* 記事系統の前処理
* @param Request $request
* @return string
*/
protected function beforeFilter(Request $request): void
protected function beforeFilter(Request $request): string
{
parent::beforeFilter($request);
$template_path = parent::beforeFilter($request);
if (strlen($template_path) > 0) {
return $template_path;
}

// ブログID指定があるかチェック
$blog_id = $request->getBlogId();
Expand Down Expand Up @@ -76,6 +80,8 @@ protected function beforeFilter(Request $request): void
$entries_model = new EntriesModel();
$entries_model->updateReservation($blog_id);
$entries_model->updateLimited($blog_id);

return "";
}

/**
Expand Down
21 changes: 17 additions & 4 deletions app/src/Web/Controller/User/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace Fc2blog\Web\Controller\User;

use Fc2blog\Model\BlogsModel;
use Fc2blog\Service\AccessBlock;
use Fc2blog\Web\Controller\Controller;
use Fc2blog\Web\Fc2BlogTemplate;
use Fc2blog\Web\Request;
Expand All @@ -12,6 +13,22 @@

abstract class UserController extends Controller
{
protected function beforeFilter(Request $request): string
{
// 親のフィルター呼び出し
$template_path = parent::beforeFilter($request);
if (strlen($template_path) > 0) {
return $template_path;
}

// IPアドレスからアクセス元の国を推定してのブロック
if ((new AccessBlock())->isUserBlockIp($request)) {
return $this->error403();
}

return "";
}

/**
* 管理画面ログイン中のブログIDを取得する
*/
Expand Down Expand Up @@ -80,9 +97,6 @@ protected static function getEntryPasswordKey(string $blog_id, int $entry_id): s
*/
protected function renderByFc2Template(Request $request, string $template_file_path): string
{
if (is_null($template_file_path)) {
throw new InvalidArgumentException("undefined template");
}
if (!is_file($template_file_path)) {
throw new InvalidArgumentException("missing template");
}
Expand All @@ -94,7 +108,6 @@ protected function renderByFc2Template(Request $request, string $template_file_p

// テンプレートをレンダリングして返す
ob_start();
/** @noinspection PhpIncludeInspection */
include($template_file_path);
return ob_get_clean();
}
Expand Down
11 changes: 11 additions & 0 deletions app/src/Web/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ public function getReferer(): string
return $this->server['HTTP_REFERER'] ?? '';
}

/**
* アクセスIPアドレスを返却 取得できなかった場合は空文字を返却
* @return string
*/
public function getClientIpAddress(): string
{
// TODO support X_FORWARDED_FOR and other.
// TODO どの環境変数を「信用するか」を設定する項目が必要
return $this->server['REMOTE_ADDR'] ?? '';
}

public function getPath()
{
return $this->path;
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"nikic/php-parser": "^4.10",
"tuupola/base62": "^2.1",
"mibe/feedwriter": "^1.1",
"swiftmailer/swiftmailer": "^6.0"
"swiftmailer/swiftmailer": "^6.0",
"maxmind-db/reader": "~1.0"
},
"config": {
"vendor-dir": "app/vendor"
Expand Down
Loading