Интеграция GNOME Online Accounts с сервисами Яндекса в ОС «МСВСфера» 9
Весной 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.
Разрешить получать почту сторонним сервисам по IMAP
Теперь при подключенной учётной записи Яндекса Evolution получает информацию из GNOME Online Accounts и принимает почту. Никаких дополнительных действий по авторизации предпринимать не нужно.
Вот так отображается Яндекс Почта в Evolution
Интеграция с почтой оказалась довольно простой. И, воодушевлЁнные, мы приступили к следующему этапу — интеграции с календарём.
Интеграция с календарём
Первые сложности возникли с Яндекс Календарём, так как у него есть свои особенности передачи заголовка авторизации. Для проверки работоспособности 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);
Теперь календарь работает.
Так выглядит Яндекс Календарь в Evolution
Так выглядят задачи из Яндекс Календаря в Evolution
Также Яндекс Календарь стал доступным из окружения GNOME.
Так выглядят задачи из Яндекс Календаря в GNOME-календаре
Так выглядят задачи из Яндекс Календаря в окружении 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.
Так выглядят Яндекс Диск, отображаемый в файловом менеджере 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...
...
/addressbook/user1%40yandex.ru/addressbook/ ...
HTTP/1.1 200 OK
При аутентификации типа 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 конференции разработчиков свободных программ. Вы можете посмотреть и послушать доклад.
Ссылки на проекты:
Вы можете бесплатно протестировать последнюю версию ОС «МСВСфера» 9 от «Инферит» для серверов и рабочих станций, скачав нужный образ дистрибутива на нашем официальном сайте по ссылке.
Приглашаем подписаться на наш телеграм-канал «Инферит ОС».
Если есть вопросы и предложения — мы будем рады вашим отзывам.
Спасибо за внимание!