Весной 2024 года российский производитель оборудования и ПО для ИТ-инфраструктуры и информационной безопасности «Инферит» выпустил новую версию операционной системы «МСВСфера» 9 на базе ядра Linux. В продуктовом портфеле вендора две редакции: ОС «МСВСфера АРМ» 9, разработанная для рабочих станций, и версия ОС «МСВСфера Сервер» 9 для серверной инфраструктуры.
Наша миссия — сделать использование компьютера простым, безопасным и приятным для каждого пользователя. Интуитивно понятный интерфейс и широкий спектр функций российской операционной системы «МСВСфера АРМ» 9 позволяет легко и эффективно решать задачи любой сложности, учиться, работать и отдыхать.
В этой статье мы хотим рассказать о том, как интегрировали сервисы Яндекса в ОС «МСВСфера АРМ» 9.
GNOME Online Accounts
GNOME Online Accounts представляет собой удобный инструмент управления учётными записями пользователя в онлайн-сервисах в среде рабочего стола GNOME. Пользователь может подключиться к своим аккаунтам и управлять контактами и календарём, отправлять почту, работать с документами и совершать другие действия прямо в среде рабочего стола.
На момент разработки GNOME Online Accounts не предоставлял доступ к российским сервисам, таким как Яндекс или ВКонтакте. Мы не нашли в российском сегменте подобных доработок, поэтому решили добавить их поддержку. И начали с поддержки аккаунтов Яндекса.
Доработка кода GNOME Online Accounts для поддержки сервисов Яндекса
Для интеграции с сервисами Яндекса решили использовать Yandex Auth через OAuth-токен.
Мы сформировали список поддерживаемых свойств провайдера, адреса авторизации и получения токена, характерных для Яндекса.
Например, идентификатор пользователя извлекается из поля default_email, возвращаемого при запросе страницы https://login.yandex.ru/info.
Давайте теперь посмотрим, как выглядит список сервисов, к которым можно подключиться.
После добавления аккаунта через центр управления, он появляется в списке доступных системе аккаунтов.
[user1]@gmail.com at Google (google)
AccessToken: [googletoken]
ClientId: [clientid].apps.googleusercontent.com
ClientSecret: ****
Mail 0x55a1ff539d50
Mail name
Mail addr [user1]@gmail.com
Mail imap imap.gmail.com
Mail imap host [user1]@gmail.com
[user2]@yandex.ru at Yandex (yandex)
AccessToken: [yandextoken]
ClientId: **********
ClientSecret: ****
Mail 0x55a1ff56d950
Mail name
Mail addr [user2]@yandex.ru
Mail imap imap.yandex.ru
Mail imap host [user2]@yandex.ru
Интеграция с почтой
В пакет ОС «МСВСфера АРМ» 9 входит почтовый клиент Evolution, который имеет встроенную интеграцию с GNOME Online Accounts — поэтому мы выбрали его для проверки интеграции с почтой. Сразу подключиться к Яндексу не удалось, Evolution не увидел нового провайдера.
Тогда мы решили вручную добавить провайдера Яндекс и для этого доработали пакет evolution-data-server. Теперь Evolution может извлекать из GNOME Online Accounts токен и параметры учётной записи и далее взаимодействовать с необходимыми сервисами, используя функции провайдера.
Для корректной работы почты также оказалось необходимым в Яндекс Почте разрешить получать почту сторонним сервисам по IMAP.
Теперь при подключенной учётной записи Яндекса Evolution получает информацию из GNOME Online Accounts и принимает почту. Никаких дополнительных действий по авторизации предпринимать не нужно.
Интеграция с почтой оказалась довольно простой. И, воодушевлЁнные, мы приступили к следующему этапу — интеграции с календарём.
Интеграция с календарём
Первые сложности возникли с Яндекс Календарём, так как у него есть свои особенности передачи заголовка авторизации. Для проверки работоспособности API Яндекс Календаря мы написали скрипт, который должен прочитать события:
#!/usr/bin/env ruby
$LOAD_PATH.unshift File.expand_path("lib", __dir__)
require "calendav"
u = 'username@yandex.ru'
credentials = Calendav::Credentials::Standard.new(
host: "https://caldav.yandex.ru/",
username: u,
password: "**************token***************",
authentication: :oauth_token
)
client = Calendav.client(credentials)
puts "principals", client.principal_url
puts "calendars"
calendars = client.calendars.list
calendars.each do |calendar|
puts calendar.url
puts calendar.display_name
end
Тип аутентификации был выбран как :oauth_token, он формирует OAuth-заголовок:
def authenticated
case credentials.authentication
when :basic_auth
HTTP.basic_auth(user: credentials.username, pass: credentials.password)
when :bearer_token
HTTP.auth("Bearer #{credentials.password}")
when :oauth_token
HTTP.auth("OAuth #{credentials.password}"))
else
raise "Unexpected authentication approach:"\
"#{credentials.authentication}"
end
end
При установке параметра authentication как :oauth_token в заголовке Authorization передается следующее значение OAuth given_in_password_token. Работа скрипта выглядит так:
[user@gui calnew]$ '/home/user/ruby_code/calnew/test.rb'
principals
https://caldav.yandex.ru/principals/users/user%40yandex.ru/
calendars
https://caldav.yandex.ru/calendars/user%40yandex.ru/events-23342418/
test1
https://caldav.yandex.ru/calendars/user%40yandex.ru/events-23426104/
my1
https://caldav.yandex.ru/calendars/user%40yandex.ru/todos-6109195/
Не забыть
Если заменить authentication на :bearer_token, то получится такой вывод:
[user@gui calnew]$ '/home/user/ruby_code/calnew/test.rb'
/home/user/ruby_code/calnew/lib/calendav/error_handler.rb:19:in `call': 401 Unauthorized (Calendav::RequestError)
Так стало понятно, что в Evolution для провайдеров была задана Bearer-авторизация в файле e-soup-auth-bearer.c. Алгоритмы данного типа авторизации подходят для Яндекса за исключением ключевого слова Bearer в заголовке. Так выглядит оригинал функции, которая формирует заголовок:
static gchar *
e_soup_auth_bearer_get_authorization (SoupAuth *auth,
SoupMessage *message)
{
ESoupAuthBearer *bearer;
gchar *res;
bearer = E_SOUP_AUTH_BEARER (auth);
g_mutex_lock (&bearer->priv->property_lock);
res = g_strdup_printf ("Bearer %s", bearer->priv->access_token);
g_mutex_unlock (&bearer->priv->property_lock);
return res;
}
Строка
res=g_strdup_printf("Bearer %s", bearer→priv→access_token);
формирует заголовок с ключевым словом Bearer, а нужно с OAuth. Поэтому во избежание копирования всего файла мы сделали небольшую правку, которая позволила задавать специфический ключ для заголовка авторизации.
Новый код выглядит так:
static gchar *
e_soup_auth_bearer_get_authorization (SoupAuth *auth,
SoupMessage *message)
{
ESoupAuthBearer *bearer;
gchar *res;
bearer = E_SOUP_AUTH_BEARER (auth);
g_mutex_lock (&bearer->priv->property_lock);
if (!bearer->priv->is_custom_bearer[0])
res = g_strdup_printf ("Bearer %s", bearer->priv->access_token);
else
res = g_strdup_printf ("%s %s", bearer->priv->is_custom_bearer, bearer->priv->access_token);
g_mutex_unlock (&bearer->priv->property_lock);
return res;
}
Также была добавлена проверка на то, что Bearer является настраиваемым и вставка собственного заголовка авторизации вместо Bearer:
res=g_strdup_printf("%s %s, bearer→priv→is_custom_bearer, bearer→priv→access_token);
Теперь календарь работает.
Также Яндекс Календарь стал доступным из окружения GNOME.
Интеграция с диском
Для интеграции с Яндекс Диском мы рассматривали два варианта: подключение через Яндекс REST API и через Яндекс WebDAV API.
Основной пакет, который реализует подключение виртуальных файловых систем в GNOME, это — gvfs. В этом пакете уже имеется инструментарий по работе с WebDAV-протоколом (реализован для Nextcloud). Поэтому в первую очередь мы заинтересовались именно WebDAV-протоколом от Яндекса.
Для проверки работы WebDAV от Яндекса использовался простой скрипт, выводящий список файлов корневого каталога диска.
#!/usr/bin/env ruby
$LOAD_PATH.unshift File.expand_path("net_dav/lib", __dir__)
require 'net/dav'
url = "https://webdav.yandex.ru/"
user = "[username]"
pass = "[token]"
dav = Net::DAV.new(url, :curl => false)
dav.verify_server = false
dav.credentials(user, pass)
dav.find('.',:recursive=>true,:suppress_errors=>true,:filename=>/.*/) do | item |
puts "Checking: " + item.url.to_s
end
Для реализации аутентификации был добавлен код в модуль Net проверочного тестового скрипта.
case @authorization
when :basic
req.basic_auth @user, @pass
when :digest
digest_auth(req, @user, @pass, response)
when :oauth
oauth_auth(req, @user, @pass)
end
…где функция oauth_auth возвращала следующее:
def oauth_auth(request, user, password)
request['Authorization'] = "OAuth " + password
end
Из вывода видно, что аутентификация, характерная для Яндекса, работает.
Для определения типа авторизации используется заголовок www-authenticate, который возвращает строку WWW-Authenticate: Basic realm="Yandex.Disk", где аутентификация идентифицирует себя как Basic, но в качестве realm указывается Yandex.Disk. Поэтому для идентификации типа авторизации в WebDAV для Яндекса был добавлен следующий код:
when Net::HTTPUnauthorized then
response.error! unless @user
response.error! if req['authorization']
new_req = clone_req(req.path, req, headers)
if response['www-authenticate'] =~ /^basic/i
if disable_basic_auth
raise "server requested basic auth, but that is disabled"
end
if response['www-authenticate'] =~ /Yandex/i
@authorization = :oauth
else
@authorization = :basic
end
else
@authorization = :digest
end
return handle_request(req, headers, limit - 1, &block)
Получается, что кроме анализа на наличие Basic, нужно выполнять еще и анализ на наличие Yandex. Это довольно важный момент, так как в реализации WebDAV в gvfs-пакете алгоритм идентичный.
Итак, алгоритм работы обрисован, код, отвечающий за WebDAV в gvfs-пакете найден.
Остаётся дело за реализацией. И после внесения изменений и пересборки пакета мы видим, что интеграция GNOME Online Accounts с Яндекс Диском работает как требуется и диск отображается в файловом менеджере Nautilus.
На иллюстрации выше:
Область 1 — меню, в котором отображаются подключённые через GNOME Online Accounts источники данных.
Область 2 — содержимое Яндекс Диска.
Область 3 — в адресной строке отображается содержимое (корень) смонтированного Яндекс Диска.
Подключённый Яндекс Диск отображается как папка на рабочем столе:
Считаем, что интеграция с Яндекс Диском прошла успешно.
Интеграция с адресной книгой
Следующий этап — интеграция адресной книги Яндекса.
Evolution поддерживает протокол carddav, поэтому подходит для интеграции с данным сервисом. В GNOME Online Accounts можно задать поддержку адресной книги или контактов, но при попытке доступа с OAuth-токеном к контактам Яндекса получить эти данные не получилось. Пришлось проводить более глубокое исследование.
В приложении Яндекса в списке разрешений кроме calendar:all нет ничего, что относится к контактам. В документации Яндекса получилось найти информацию по «Контактам CardDAV».
Для проверки доступа мы создали тестовый аккаунт и написали два скрипта на Ruby.
Первый скрипт для проверки доступа с помощью пароля, сгенерированного app-passwords, обращается к адресной книге аккаунта в Яндексе посредством carddav-протокола с помощью базовой авторизации.
#!/usr/bin/env ruby
$LOAD_PATH.unshift File.expand_path("lib", __dir__)
require 'carddav'
service = Carddav.service(:yandex1, 'user1@yandex.ru',
'password_generated_by_yandex_app_passwords')
p service.addressbook_url
Второй скрипт для проверки доступа через OAuth и токен обращается к адресной книге аккаунта в Яндексе посредством carddav-протокола с помощью OAuth-авторизации.
#!/usr/bin/env ruby
$LOAD_PATH.unshift File.expand_path("lib", __dir__)
require 'carddav'
service = Carddav.service(:yandex, 'user1@yandex.ru',
'token_generated_by_goa')
p service.addressbook_url
Типы авторизации :yandex и :yandex1 — это два типа формирования авторизационного заголовка (Basic или OAuth).
def request(url, method: 'PROPFIND', depth: 0)
req = Curl::Easy.perform url.to_s do |c|
c.headers['Content-Type'] = 'application/xml'
c.headers['Depth'] = depth
if @service == :yandex
#c.userpwd = "OAuth #{password}".strip
c.headers['Authorization'] = "OAuth #{password}".strip
elsif @service == :yandex1
pss = Base64.encode64("#{username}:#{password}").strip
c.headers['Authorization'] = "Basic #{pss}"
#c.userpwd = "#{username}:#{password}"
else
c.userpwd = "#{username}:#{password}"
end
c.set :customrequest, method
c.set :postfields, yield if block_given?
end
Нам было интересно, как отработают оба скрипта.
В листинге ниже вывод, сформированный первым скриптом, с паролем, сгенерированным через app-passwords для CardDAV — при аутентификации типа Basic от сервиса приходит ответ HTTP/1.1 207 Multi-Status. Это корректный и ожидаемый ответ.
https://carddav.yandex.ru/addressbook/user1%40yandex.ru/addressbook/
{"Content-Type"=>"application/xml", "Depth"=>1, "Authorization"=>"Basic **….**"}"}
+++++++++++++++
HTTP/1.1 207 Multi-Status
Content-Length: 1049
Content-Type: text/xml;charset=utf-8
DAV: 1,addressbook,calendar-access,...
Date: Tue, 11 Jul 2023 16:49:05 GMT
Set-Cookie: _yasc=NjF9Txq0zqfcgvr67ZyjuNB5Z7nc...
<?xml version='1.0' encoding='utf-8'?>
<D:multistatus xmlns:D="DAV:">...
<D:response><href xmlns="DAV:">/addressbook/user1%40yandex.ru/addressbook/</href>...
<status xmlns="DAV:">HTTP/1.1 200 OK</status>
При аутентификации типа OAuth в листинге ниже можно увидеть, что ответ приходит иной — HTTP/1.1 404 Not Found. И далее работа тестового скрипта прекращается. Это не даёт возможности использовать CardDAV с OAuth-токеном.
https://carddav.yandex.ru/addressbook/user1%40yandex.ru/addressbook/
{"Content-Type"=>"application/xml", "Depth"=>1, "Authorization"=>"OAuth **...**"}"}
+++++++++++++++
HTTP/1.1 404 Not Found
DAV: 1,addressbook,calendar-access,...
Date: Tue, 11 Jul 2023 16:57:29 GMT
Set-Cookie: _yasc=tj9L+WR9sAD...
Transfer-Encoding: chunked
X-Request-Id: 16890946496…
То есть при запуске с Basic-авторизацией в ответ на запрос к https://carddav.yandex.ru/addressbook/user1%40yandex.ru/addressbook/ был возвращен список адресных книг, а при OAuth — 404. С этой же ошибкой сталкивается и Evolution при попытке получить список адресных книг.
Получается, что для работы Адресной книги OAuth не подходит, нужен отдельный пароль и иной способ авторизации. Из публичной документации Яндекса не удалось понять, было ли такое поведение задумано и контакты не должны быть доступны при OAuth-авторизации, или же не было добавлено разрешение для приложения.
Мы отправили запрос в техническую поддержку Яндекса и приостановили доработку интеграции с Адресной книгой.
Как теперь выглядит процесс регистрации и доступа к сервисам Яндекса в ОС «МСВСфера АРМ» 9
Регистрация
Переключатели в настройках учётной записи позволяют быстро включить или выключить необходимый функционал.
Почта
Календарь
Диск
Что дальше?
Мы планируем отправить изменения в репозитории доработанных пакетов: gnome-online-accounts, evolution-data-server, gvfs.
Планируем завершить интеграцию Адресной книги Яндекса.
Добавить поддержку REST API Яндекса.
Оптимизировать работу с протоколом webDAV для Яндекс Диска.
Добавить поддержку других отечественных сервисов.
Заключение
Мы рассказывали о том, как мы интегрировали GNOME Online Accounts с сервисами Яндекса на ХIX конференции разработчиков свободных программ. Вы можете посмотреть и послушать доклад.
Ссылки на проекты:
Исходные коды gnome‑online‑accounts: https://git.inferitos.ru/rpms/gnome‑online‑accounts/src/branch/i9
Исходные коды evolution‑data‑server: https://git.inferitos.ru/rpms/evolution‑data‑server/src/branch/i9
Исходные коды gvfs: https://git.inferitos.ru/rpms/gvfs/src/branch/i9
Вы можете бесплатно протестировать последнюю версию ОС «МСВСфера» 9 от «Инферит» для серверов и рабочих станций, скачав нужный образ дистрибутива на нашем официальном сайте по ссылке.
Приглашаем подписаться на наш телеграм‑канал «Инферит ОС».
Если есть вопросы и предложения — мы будем рады вашим отзывам.
Спасибо за внимание!
twinpigs
Вы же предложите реквест в апстрим, правда?
Skoree Автор
да, я планирую сделать реквест в апстрим