Создание плагина аутентификации пользователей во внешней информационной системе в Мудл 3.0

Создание плагина аутентификации пользователей во внешней информационной системе в Мудл 3.0

от Ivan Novostruev -
Количество ответов: 3

Здравствуйте, уважаемые участники форума!

Нужна ваша помощь в решении непростой технической задачи, а именно, создании нового плагина аутентификации в системе Мудл 3.0 для проверки учетных данных пользователей, но не в базе данных Мудл, а во внешней информационной системе (ИС).


Опишу, в общем, метод аутентификации, который требуется реализовать с помощью данного плагина:

Когда пользователь в форме ввода на странице Входа в систему будет вводить свои логин и пароль, сценарий /login/index.php должен делать POST-запрос веб-серверу внешней ИС, с целью передать ему данные логина и пароля пользователя для проверки их подлинности в этой ИС (полученные таким образом значения логина и пароля будут сравниваться информационной системой с соответствующими значениями из своей базы данных).

Далее, некий сценарий на стороне ИС будет генерировать ответ и отправлять его в виде POST-запроса обратно в Мудл (по адресу site.ru/login/index.php).  Примечание: В генерируемом ответе будет содержаться не только результат проверки логина и пароля, но и дополнительная информация о пользователе, необходимая для его создания в Мудл или обновления данных его профиля.

На основе полученного ответа плагин должен либо пускать пользователя в Систему(Мудл), либо отказывать ему, выводя соответствующее сообщение об ошибке. 

Если пользователь успешно прошёл аутентификацию во внешней ИС, то Мудл должен создать соответствующего пользователя, либо, если этот пользователь уже существует в Системе, то обновить данные его профиля (данные полей профиля пользователя). Пароли пользователей не должны сохраняться внутри базы данных Мудл. Вкратце всё.


"Тело" плагина уже создано в соответствии с Документацией разработчика. Теперь нужно вдохнуть в него "душу".

Плагин аутентификации


Теперь самое главное - вопросы, касающиеся собственно плагина:

1. В каком методе класса auth_plugin_myplugin из файла /auth/my_plugin/auth.php нужно прописать инструкцию(и) для отправки данных логина и пароля в виде POST-запроса серверу внешней ИС.

2. Каким образом будет осуществляться задержка выполнения сценария login/index.php, до тех пор, пока веб-сервером Мудл не получен ответ (в виде POST-запроса) от внешней информационной системы?

3. Если у кого-то есть опыт в создании плагинов аутентификации и желание поделиться этим опытом с другими, прошу вас дать о себе знать (если что, в профиле указан мой Скайп, стучитесь  =)), или напишите свой, чтобы я мог с вами связаться). Буду благодарен за отклик.


Для наглядности прикладываю схему аутентификации, которая частично отражает принцип работы плагина:

Схема аутентификации пользователей Moodle во внешней ИС


Думаю, данная тема будет полезна не только мне.

Спасибо за прочтение и за ваши ответы!

В ответ на Ivan Novostruev

Re: Создание плагина аутентификации пользователей во внешней информационной системе в Мудл 3.0

от Vadim Dvorovenko -
Изображение пользователя Developers Изображение пользователя Майнтейнер перевода

Здравствуйте. Я был озадачен подобной темой ещё лет семь назад, поэтому расскажу вам об эволюции в своих представлениях об этой теме. Пара плагинов аутентификации были моими первыми разработками для moodle 1.9

Когда я делал первую связку между ИС вуза и moodle, думал примерно также как и вы. Я сразу перешагнул попытку подключаться из moodle к бд ИС соответствующим модулем moodle, чтобы системы могли быть максимально автономными и мобильными, легко сосуществую даже будучи разделенными брандмауэром. Единственное, что я использовал GET-запросы, но это неправильный вариант, так как логины/пароли в этом случае фиксируются в логах прокси и веб-серверов, а это не очень правильно - получив доступ к файлу лога веб-сервера можно получить пароль админа в чистом виде. Но я просто отключил логгирование в веб-сервере.

Основные ошибки этого подхода в следующем: пароли вообще никогда не должны передаваться в открытом виде между системами. Любое предложение одной системы использовать пароль от другой системы - уже угроза безопасности, даже если обе системы ваши. Так, например, для одного проекта, когда не было доступа к базе данных сотрудников, я делал следующим образом: плагин запрашивал логин/пароль, обращался на другой сайт с этим логином/паролем, и в случае, если он подходил, парсил страницу, находил место, выводится Фамилия и Имя текущего пользователя и обновлял этими данными текущего пользователя. Для пользователя это выглядит как использование одних и тех же учетных данных в двух системах, но на деле это настоящее воровство паролей.

Поэтому нормальные системы всегда делают проверку паролей сами. Для примера vk и google. Как это должно работать: Когда вы заходите в систему, вас перебрасывает на сервер авторизации вашей ИС. При этом в запросе передается адрес, куда вернуть данные. Внешняя система проверяет адрес для возвращения данных по списку доверенных систем, и если он из списка, отображает форму проверки логина/пароля. После этого ИС возвращает по адресу для возвращения данных некий секретный токен в get-запросе. В зависимости от реализации токен может сразу содержать данные пользователя и электронную цифровую подпись (хэш-сумму) информационной системы, а может только некий идентификатор сессии. ЭЦП нужна, чтобы нельзя было подделать ответ ИС и зайти в moodle от имени другого пользователя. Если передается только идентификатор сессии, то moodle обращается с ним в ИС по альтернативному каналу (минуя браузер пользователя), проверяет, действительно ли логин/пароль проверялся, и забирает данные пользователя.

Преимущество этого способа в том, что вы можете сделать совершенно иную страницу проверки логина/пароля. Например, предложив студенту выбрать факультет, группу, номер зачетки из списка и указать дату рождения в качестве пароля. Сомнительна потребность, но в moodle таких изощренных вариантов точно не сделать. Минус в том, что сложнее в реализации и если вы захотите таким образом авторизовать несколько систем, то придется писать плагин авторизации для нескольких из них.

Я реализовывал и первый, и второй способ. Но вот в чем беда - когда у вас только ИС и Moodle, то можно увязать множеством способов. Когда у вас появляются дополнительные системы, а они точно начнут появляться, добавлять каждую новую систему становится всё сложнее, потому у всех авторизация сделана по-разному. Дополнительные системы это, к примеру, портал вуза (есть решение на битриксе), система портфолио, рейтинг студентов и ещё больше систем для преподавателей. Многие и этих систем закрыты и вы просто не сможете к ним приспособить указанные выше способы авторизации.

Поэтому сейчас я могу вам сказать следующее - вам нужен LDAP-сервер. Можно открытый, можно Active Directory. Ваша ИС должна записывать данные пользователей на этот сервер, а он уже должен служить источником для авторизации как для moodle, так и для других будущих систем - все нормальные системы работают с ldap. В будущем, если вы увидите, что какая-то система не работает с ldap (хотя бы в корпоративной версии), бегите прочь от этой системы, это какая-то несерьёзная разработка. У LDAP есть ещё одно преимущество. Вы можете в одной системе собрать учетный данные из совершенно разнородных систем. Если у вас разные базы данных для студентов, преподавателей и сотрудников вуза, каждая из этих систем может выложить эти логины/пароли в LDAP, а оттуда любая система их может забрать.

Правда у LDAP тоже есть недостатки. Большинство реализаций LDAP-аутентификации в разных системах подразумевает, что логин пользователя является постоянным и является ключевым полем. Но на практике это не так. Пользователь может сменить фамилию (если логин связан с фамилией), номер зачетки, читательского билета, перейти на другую специальность (если логин связан с чем-то из перечисленного) и т.п. Если логины в вашей ИС НИКОГДА не меняются, то всё хорошо. В идеале логин должен быть ни к чему не привязанным набором цифр, причем менять под себя должно быть запрещено даже админам системы.

Но у нас иначе. А плагин LDAP moodle при смене логина создаст новую учетку, а не переименует старую. Единственный обход - это если админ изменит логин пользователя разом во всех системах при смене его в ldap - но систем много и это может стать проблемой. Ну или сделать свой плагин аутентификации, который грамотно разрулит эту ситуацию. И тут мы возвращаемся к тому, с чего начали. Замкнутый круг.

Теперь ответы на ваши вопросы

1. Для отправки на другой сервер запроса используйте расширение PHP curl.

2. В отличие Javascript и node.js, php - это синхронный язык программирования. Он итак будет ждать ответа сервера при выполнении скрипта, а в случае долгого отсутствия ответа выдаст ошибку curl. Поэтому таймаут лучше ставить небольшой, секунд 10-30.

И ещё замечание. С учетом того, что система у вас, видимо, самодельная, то и сбои в её работе возможны, что приведет к невозможности входа в moodle. Если вы станете хранить пароли в moodle, то сможете проверять по ним в случае отсутствия связи с ИС. Стандартные плагины мудла не реализуют такой функционал, но в самодельном вы можете это реализовать. Так, например, после очередной перенастройки сети у меня выяснилось, что нет связи между moodle и ИС только недели через две от двоечников, которые ни разу не входили в систему до этого, а так бы 100% пользователей не смогли бы пользоваться системой до решения проблемы. LDAP-сервер тоже решает эту проблему, так как работает, даже когда ваша ИС не работает, плюс можно поднять два сервера и настроить между ними репликацию. В случае AD достаточно двух контроллеров домена, репликация будет автоматом.

Когда решите, каким путем идти, смогу подсказать что-то более конкретное. И все вопросы задавайте на форуме

В ответ на Vadim Dvorovenko

Re: Создание плагина аутентификации пользователей во внешней информационной системе в Мудл 3.0

от Ivan Novostruev -

Спасибо большое за помощь, Вадим!  Ваш ответ помог мне написать основную часть плагина, отвечающую за аутентификацию пользователей! У Вас не сообщение, а просто кладезь полезной информации! Побольше бы таких людей, как Вы!


Хотел бы немного рассказать о плагине, так как, возможно, перед кем-то встанет похожая задача, и тогда он сможет воспользоваться имеющимися здесь наработками.

Целью разработки плагина было создание единого пространства аутентификации в университете.

Вот список осуществляемых плагином функций:

  1. Аутентификация пользователей во внешней информационной системе и создание для них учётных записей в Мудл. В случае, если ИС по каким-либо причинам недоступна, вход в Систему осуществляется по внутреннему паролю, хранящемуся в БД Мудл. Основная сложность состояла в том, что в ИС у одного пользователя может быть несколько пар логин-пароль, и требовалось привести их к одной учётной записи в Мудл.
  2. Автоматическое заполнение и обновление профиля пользователя данными, получаемыми из ИС. Данные о пользователе подразделяются на 2 группы: 1) данные о студенте: факультет, направление подготовки, курс, группа, номер зачётки, форма обучения, квалификация и др.; 2) данные о преподавателе: кафедра, учёное звание, учёная степень.
  3. Автоматическая запись пользователей (студентов и преподавателей) на предназначенные для них курсы в соответствие с учебной нагрузкой, размещённой во внешней ИС, и назначение пользователям необходимой роли на этих курсах.
  4. Автоматическое создание групп внутри курсов и запись студентов в эти группы.

В придачу, вот несколько ссылок на перевод страниц из Документации для разработчиков, касающихся плагинов аутентификации:

Плагины аутентификации

API Аутентификации

FAQ по Аутентификации

От себя добавлю, что мне лично очень помогла эта информация при разработке плагина. Также при разработке необходимо базовое знание PHP и, в том числе, принципов объектно-ориентированного программирования на PHP.

Для тех, кто хочет детально ознакомиться с логикой работы плагина, в моём профиле представлена ссылка на его исходный код на GitHab'е.


Ещё раз огромный Вам респект за помощь, Вадим!

Желаю Вам и всем мудлерам, собравшимся на этом форуме, успеха в труде! улыбаюсь

В ответ на Ivan Novostruev

Re: Создание плагина аутентификации пользователей во внешней информационной системе в Мудл 3.0

от Vadim Dvorovenko -
Изображение пользователя Developers Изображение пользователя Майнтейнер перевода

Посмотрел Ваш плагин. Вами проделана действительно колоссальная работа. Есть лишь небольшие замечания.

1. В случае отказа функционирования ИАС, функция аутентификации вернет false и пользователь Moodle будет получать стандартное сообщение, что пароль неверен. Если у Вас ИАС - это единственный плагин аутентификации, то, возможно, стоит придумать способ вывести сообщение о том, что проблема в ИАС. Хотя у  меня используется альтернативное решение. Я сохраняю пароли пользователей в moodle и, в случае, когда нет соединения с ИАС, проверяю пароль по внутренней базе.

2. У Вас can_edit_profile = true. Но при этом вы не выводите в config.html таблицу настройки блокировки полей. Хотя, уверен, менять Фамилию, Имя и Отчество, в moodle не должно быть разрешено. Вы можете задать в конструкторе что-то вроде

        $this->config = new stdClass();
$this->config->field_updatelocal_firstname = 'onlogin';
$this->config->field_lock_firstname = 'locked';
$this->config->field_updatelocal_lastname = 'onlogin';
$this->config->field_lock_lastname = 'locked';
$this->config->field_updatelocal_idnumber = 'onlogin';
$this->config->field_lock_idnumber = 'locked';
$this->config->field_updatelocal_email = 'oncreate';
$this->config->field_lock_email = 'unlocked';
$this->config->field_updatelocal_city = 'oncreate';
$this->config->field_lock_city = 'unlocked';
$this->config->field_updatelocal_country = 'oncreate';
$this->config->field_lock_country = 'unlocked';
$this->config->field_updatelocal_lang = 'oncreate';
$this->config->field_lock_lang = 'unlocked';
3. В не экранируете логин и пароль при передаче в get-запросе. Это значит, что как только у пользователя в пароле окажется русская буква или, например, знаки %, ?, & - у Вас всё сломается. Не мудрите, используйте объект new moodle_url('сервер и путь', array(массив параметров)) и тут же получите безопасную строку, где всё правильно экранировано.
4. В строке 916 вы используете get_record_sql, там, где достаточно get_record. Это не критично, get_record всё-равно потом сам вызывает get_record_sql, но лучше так не делать - код будет компактнее, легче читаться и меньше сделаете ошибок. Сравните:
$DB->get_record_sql('SELECT * FROM {user_info_data} WHERE data = ? AND fieldid = ?',array($pers_id, self::FIELDID_PERSID))
$DB->get_record('user_info_data', array('data' => $pers_id, 'fieldid' => self::FIELDID_PERSID))
Но если Вы вдруг делаете сложный sql-запрос, используйте вместо вопросиков именованные параметры -
$DB->get_record_sql('SELECT * FROM {user_info_data} WHERE data = :data AND fieldid = :fieldid', array('fieldid' =>self::FIELDID_PERSID, 'data' => $pers_id))
Это позволит передавать параметры в массив в любом порядке и не запутаться, когда запрос сложный и параметров много.