Обработка метаданных и цифровое подписывание HTML-форм Дмитрий Котеров ([email protected]) архитектор и главный разработчик MoiKrug.ru, компания Яндекс Что такое метаданные? Фамилия: <input type="text" name="lastname" /> Пол: <input type="radio" name="gender" value="m" /> Муж <input type="radio" name="gender" value="f" /> Жен • Данные – то, что ввели array( "lastname" => "Пупкин", "gender" => "m" ) • Метаданные – "данные о структуре данных" array( "lastname" => array( "type" => "text", "caption" => "Фамилия", "dbfield" => "p_lastname" ), "gender" => array( "type" => "single", "dbfield" => "p_gender", "elements" => array( "m" => "Муж", "f" => "Жен", ) ) ) www.rit2007.ru Типы полей с точки зрения метаданных • Текст – <input type="text">, <input type="hidden">, <textarea> • Файл (закачка) – <input type="file"> • Единичный выбор – <select></select> – <input type="radio">… • Множественный выбор (аналогично) – <select multiple></select> – <input type="checkbox">… • Действие – <input type="submit">, <input type="image"> www.rit2007.ru Web- или GUI-приложение • Web-приложение – "Размазанность" – Двойная валидация (PHP, JS) – Нет доверия метаданным (неявная структура POST) – Дублирование метаданных в дизайне и обработчике – Косвенный вызов обработчиков Рисовальщик формы errors Обработчик формы • GUI-приложение – Централизованная обработка – Непосредственная валидация – "Доверие" метаданным (все в рамках одной оконной формы) – Форма "рисуется" и обрабатывается в едином месте – Мгновенные обработчики кнопок HTML Пользова тель POST GUI-приложение: форма + обработчики событий Пользователь www.rit2007.ru "Размазанность": неявные зависимости от метаданных • Различные компоненты системы – Структура БД – HTML-представление формы – Рисовальщик (код начального заполнения формы) – Обработчик (код приема данных и записи в БД) – Серверный валидатор (PHP) – Клиентский валидатор (JS) • Как их объединяют, чтобы обойти проблему – Структура БД – HTML-представление формы + клиентский валидатор – Рисовальщик + обработчик + серверный валидатор www.rit2007.ru Централизация метаданных • Репозиторий метаданных Metadata • Подход MetaForm – похож на GUI-формы HTML + Metadata HTML + JS БД Рисовальщик + обработчик + валидатор БД • Генератор по явно выделенным метаданным Рисовальщик + модификатор + обработчик + валидатор • сложность доработки • неуниверсальность www.rit2007.ru MetaForm: шаблон + метаданные <label for="l">Фамилия</label>: <input type="text" name="lastname" id="l" meta:dbfield="p_lastname" meta:validator="filled" /> Пол: <label for="m">Муж</label> <input type="radio" name="gender" value="m" id="m" meta:dbfield="p_gender" /> <label for="f">Жен</label> <input type="radio" name="gender" value="f" id="f"/> • • Метаданные объединены с представлением (как в GUI) Автоматическое извлечение метаданных Использование возможностей HTML для привязки заглавия Назначение валидаторов (серверных + клиентских) Назначение произвольных метаатрибутов Отправка формы "на себя" • Обработка HTML "на лету": • • • • – Легкость адаптации старых проектов – Совместимость с любыми имеющимися framework-ами – Неизбыточность ("а добавим-ка новое поле") www.rit2007.ru MetaForm: традиционная обработка • • • Обработка формы: – распаковка метаданных – проверка цифровой подписи – валидация – сохранение сообщений об ошибках – сохранение сообщений в сессии – "редирект на себя" Подготовка метаданных – извлечение метаданных из HTML формы, "чистка" формы – упаковка и цифровое подписывание метаданных – вставка метаданных в hiddenполе Отрисовка формы: – вставка данных из $_POST (FormPersister) – назначение клиентских валидаторов – привязка сообщений об ошибках к полям switch ($m->process()) { // Простой показ формы. case "INIT": $_POST = <достать из БД>; break; // Нажата кнопка. case "имя_кнопки": $meta = $m->getMetadata(); <записать в БД>; <запомнить сообщ. успеха>; <редирект на себя>; break; } // Произошла ошибка валидации <показать ошибки и сообщения>; <заполнить поля из $_POST>; <подготовить метаданные>; <отрисовать форму>; www.rit2007.ru MetaForm: событийная обработка // Контроллер Page class Page { function init() { // Простой показ формы. $_POST = <достать из БД>; } } function имя_кнопки() { // Нажата кнопка. $meta = $m->getMetadata(); <записать в БД>; <запомнить сообщ. успеха>; <редирект на себя>; } • switch-case хорошо подходит при переводе старой системы на MetaForm • событийная модель удобна для построения CMF <!-- Шаблон Page --> <показать ошибки и сообщения>; <заполнить поля из $_POST>; <подготовить метаданные>; <показать форму>; www.rit2007.ru Цифровая подпись метаданных • Пусть metadata – упакованные метаданные: – – – – сервер хранит секретный ключ key signed = metadata + "-" + md5(metadata + key) в hidden-поле записывается signed проверка: md5(left(signed) + key) == right(signed) • Необходимо хранить key в секрете! www.rit2007.ru Защита от подделки форм • Принудительная валидация: – hidden-поля должны быть константными: <input type="hidden" name="a" value="b"> => $_POST['a'] = 'b' – выбранный элемент из single или multiple должен содержаться в списке: <select name="s"><option value="v">текст</select> => $_POST['s'] = 'v' – форма послана именно тому скрипту, который указан в ее атрибуте action: <form action="script"> => REQUEST_URI ~ "script" – DB constraints никто не отменял! • Отмена принудительной валидации – <input type="hidden" meta:dynamic> => поле можно заполнять на JavaScript www.rit2007.ru Защита: "лишние" поля формы • Провоцирование неявной зависимости от метаданных – Имеем форму: <input type="text" name="person[firstname]" /> <input type="text" name="person[lastname]" /> – Хакер добавляет "лишнее" поле: <input type="text" name="person[firstname]" /> <input type="text" name="person[lastname]" /> <input type="hidden" name="person[is_admin]" value="1" /> – Результат: изменение неожиданного поля • MetaForm: неизвестные поля считаются подделкой www.rit2007.ru Неизбыточная валидация • Привязка валидаторов <input meta:validator="filled email"> • В полю привязано имя валидатора: – вызов валидатора на стороне сервера (PHP) function validator_название($value, $metadata) – автопривязка валидатора на стороне клиента (JavaScript) validator_название = function(value, metadata) • Привязка нескольких валидаторов <input meta:validator="filled email"> • Валидаторы должны быть ортогональными • Передача параметров валидаторам Пароль: <input type="password" name="pass" meta:validator="password_match" meta:match_field="confirm" /> Еще раз: <input type="password" name="confirm" /> www.rit2007.ru Привязка сообщений об ошибках • Ошибка валидации привязана к полю формы – Метаданные содержат ID (координаты) всех полей – Привязка ошибки к элементу на JavaScript ("модель светофора") – Фокус на ошибочном элементе • Извлечение текста ошибки по имени валидатора (языковые константы) – 'validator_email' => 'Поле "%s" должно содержать корректный E-mail!' – Подстановка имен полей (sprintf) www.rit2007.ru Резюме • Плюсы подхода MetaForm: – – – – – Родство с GUI-программированием Неизбыточность Прозрачность для любого framework-а Легкость адаптации существующих проектов Прозрачность для HTML-верстальщика • Минусы: – Смешение логики метаданных и логики представления (характерно и для GUI) www.rit2007.ru Приходите к нам работать! • МойКруг.ру теперь – сервис Яндекса • Мы расширяем свою команду! • Открыты вакансии для: – – – – верстальщиков со знанием Smarty отличных PHP-программистов опытных БД-разработчиков (PostgreSQL, Oracle) JavaScript-программистов Ждем Ваши резюме на http://moikrug.ru/hire/ www.rit2007.ru