Весной 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
Разрешить получать почту сторонним сервисам по IMAP

Теперь при подключенной учётной записи Яндекса Evolution получает информацию из GNOME Online Accounts и принимает почту. Никаких дополнительных действий по авторизации предпринимать не нужно.

Вот так отображается Яндекс Почта в Evolution
Вот так отображается Яндекс Почта в 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
Так выглядят задачи из Яндекс Календаря в Evolution
Так выглядят задачи из Яндекс Календаря в Evolution

Также Яндекс Календарь стал доступным из окружения GNOME.

Так выглядят задачи из Яндекс Календаря в GNOME-календаре
Так выглядят задачи из Яндекс Календаря в 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
Так выглядят Яндекс Диск, отображаемый в файловом менеджере 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 конференции разработчиков свободных программ. Вы можете посмотреть и послушать доклад.

Ссылки на проекты:

Вы можете бесплатно протестировать последнюю версию ОС «МСВСфера» 9 от «Инферит» для серверов и рабочих станций, скачав нужный образ дистрибутива на нашем официальном сайте по ссылке.

Приглашаем подписаться на наш телеграм‑канал «Инферит ОС».

Если есть вопросы и предложения — мы будем рады вашим отзывам.

Спасибо за внимание!

Комментарии (2)


  1. twinpigs
    27.09.2024 10:13

    Вы же предложите реквест в апстрим, правда?


    1. Skoree Автор
      27.09.2024 10:13

      да, я планирую сделать реквест в апстрим