Codeigniter 3 несколько форм с токенами ajax и csrf, работающими только в одной форме - PullRequest
0 голосов
/ 26 мая 2020

У меня есть страница администратора с несколькими настройками, каждая настройка имеет разную форму на разных вкладках.

Я использую ajax и для сохранения данных, и пока у меня не было проблем с токеном csrf когда у меня была только одна форма на странице, или когда я отключил токен csrf.

При каждом запросе ajax в контроллере генерируется новый токен и отправляется обратно на ajax, который обновляет скрытое поле с name="csrf_token", но с другими идентификаторами.

После отправки первой формы все хорошо, но когда я пытаюсь отправить другую форму, токен csrf больше не работает, я получаю сообщение «Запрошенное действие запрещено». со страницей 403 в выводе консоли даже после того, как я перезагружаю страницу и пытаюсь отправить другую форму, которая не сработала.

Есть ли способ разместить несколько форм с защитой csrf на одной странице и как с этим справиться?

Вот примеры кода Формы с ajax

<form id="upload-icon" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="csrf_token" id="csrf_token_1" value="<?php echo $this->security->get_csrf_hash(); ?>">
    <input type="file" id="favicon_image" name="favicon_image" accept=".ico">
    <button type="button" id="upload-icon-btn">Upload</button>
</form>

<form id="update-settings" method="POST">
    <input type="hidden" name="csrf_token" id="csrf_token_2" value="<?php echo $this->security->get_csrf_hash(); ?>">
    <input type="text" name="settings_one">
    <input type="text" name="settings_two">
    <input type="text" name="settings_three">
    <button type="button" id="update-settings-btn">Update settings</button>
</form>

<script>
$(document).ready(function() {
    var csrf_token = '';
    // upload favicon form
    $('#upload-favicon-form-btn').on('click', function(e) {
        e.preventDefault();

        var fd = new FormData();
        var files = $('#favicon_image')[0].files[0];
        fd.append('favicon_image', files);

        var favicon = $('#favicon_image').val();
        if (favicon == '') {
            loadModal('Warning', 'Please select <strong>favicon.ico</strong> icon file.');
        } else {
            $.ajax({
                type: 'POST',
                url: '<?php echo base_url('admin/settings/upload_ico'); ?>',
                data: fd,
                contentType: false,
                cache: false,
                processData: false,
                dataType: 'json',
                success: function(response) {
                    csrf_token = response.csrf_token;
                    $('#csrf_token_1').val(csrf_token);

                    // messages output
                },
                error: function() {
                    // error message output
                }
            });
        }
    });

    // update settings form
    $('#update-settings-btn').on('click', function(e) {
        e.preventDefault();
        $.ajax({
            type: 'POST',
            url: '<?php echo base_url('admin/settings/update_settings'); ?>',
            data: $('#update-settings').serialize(),
            dataType: 'json',
            success: function(response) {
                csrf_token = response.csrf_token;
                $('#csrf_token_2').val(csrf_token);

                // messages output
            },
            error: function() {
                // error message output
            }
        });
    });
});
</script>

Контроллер настроек

public function update_settings()
{
    $csrf_token = $this->security->get_csrf_hash();

    $this->form_validation->set_rules('settings_one', 'Setting one', 'trim|required|xss_clean');
    $this->form_validation->set_rules('settings_two', 'Setting two', 'trim|required|xss_clean');
    $this->form_validation->set_rules('settings_three', 'Setting three', 'trim|required|xss_clean');

    if ($this->form_validation->run()) {
        if ($this->Settings_model->UpdateSettings($this->input->post('settings_one'), $this->input->post('settings_two'), $this->input->post('settings_three'))) {
            $data = array(
                'success' => true,
                'message' => 'Settings updated.',
                'csrf_token' => $csrf_token
            );
        } else {
            $data = array(
                'error' => true,
                'message' => 'Settings was not updated.',
                'csrf_token' => $csrf_token
            );
        }
    } else {
        $data = array(
            'error' => true,
            'settings_one_error' => form_error('settings_one'),
            'settings_two_error' => form_error('settings_two'),
            'settings_three_error' => form_error('settings_three'),
            'csrf_token' => $csrf_token
        );
    }

    echo json_encode($data);
}

public function upload_ico()
{
    $csrf_token = $this->security->get_csrf_hash();

    $favicon_upload_path = './upload/';

    if (isset($_FILES['favicon_image']['name'])) {
        $config['upload_path'] = $favicon_upload_path;
        $config['allowed_types'] = 'ico';

        $this->load->library('upload', $config);

        if (!$this->upload->do_upload('favicon_image')) {
            $data = array(
                'error' => true,
                'message' => $this->upload->display_errors(),
                'csrf_token' => $csrf_token
            );

        } else {
            $data = array(
                'success' => true,
                'message' => 'Favicon uploaded.',
                'csrf_token' => $csrf_token
            );
        }
    } else {
        $data = array(
            'error' => true,
            'message' => 'No file selected.',
            'csrf_token' => $csrf_token
        );
    }
    echo json_encode($data);
}

Конфиг. php

$config['csrf_protection'] = TRUE;
$config['csrf_token_name'] = 'csrf_token';
$config['csrf_cookie_name'] = 'csrf_cookie_name';
$config['csrf_expire'] = 7200;
$config['csrf_regenerate'] = TRUE;
$config['csrf_exclude_uris'] = array(
    'admin/settings'
);

1 Ответ

0 голосов
/ 26 мая 2020

Я обнаружил проблему.

Я забыл отправить токен в первой форме, эти 2 строки исправляют его

var token = $('#csrf_token_1').val();  // read token value from input
fd.append('csrf_token', token);

Затем в скрипте

<script>
$(document).ready(function() {
    var csrf_token = '';
    // upload favicon form
    $('#upload-favicon-form-btn').on('click', function(e) {
        e.preventDefault();

        var fd = new FormData();
        var files = $('#favicon_image')[0].files[0];
        fd.append('favicon_image', files);
        // fix for token that should be sent to controller over ajax
        var token = $('#csrf_token_1').val();  // read token value from input
        fd.append('csrf_token', token);        // append token value to data that need to be send to controller

и далее каждый успех в ajax он должен обновлять все идентификаторы форм токеном, возвращаемым из контроллера, потому что я использую $config['csrf_regenerate'] = TRUE;, который создает новый токен для каждого запроса, поэтому я сделал js функцию, которая обновляет токен для всех идентификаторов во всех формах

// function that updates token on all forms
function updateToken(token) {
    $('#csrf_token_1, #csrf_token_2').val(token);
}

функция в использовании

success: function(response) {
    csrf_token = response.csrf_token;
    updateToken(csrf_token);
}

Возможно, это не лучшее решение, но у меня работает. Если у кого-то есть получше, пожалуйста, опубликуйте его.

...