Услуги программирования на 1С. Глоссарий HTTP аутентификации: Basic, Bearer, OAuth и другие непонятные слова

Услуги по установке 1С

Рано или поздно каждый 1С разработчик сталкивается с задачей автоматизации работы со сторонним сервисом посредством RestAPI. Одними из основных (а, как правило – и самыми запутанными) элементами любой пдобной автоматизации являются процессы авторизации и аутентификации. Токены, подписи, шифрование – как не потеряться во всем этом? Поможет данное краткое руководство

Я решил, что правильно будет разделить все приводимые понятия на уровни, чтобы шаг за шагом пройти наиболее часто встречающиеся термины, начав с самых азов. Однако, надо понимать, что здесь не будет разбора совсем базы, вроде видов http-запросов или что такое "заголовок" – только понятия и примеры по теме

Уровень 1: Авторизация и аутентификация

Это два действия, с которых начинается любая интеграция.
 

Аутентификация – это проверка, при которой действующее лицо подтверждает свою личность. Когда вы, например, вводите свои данные для входа на каком-нибудь сайте, то это, в первую очередь – аутентификация. Вы подтверждаете, что вы – это вы, используя пароль, соответствующий вашему логину

Авторизация – это процесс проверки и выдачи прав соответственно вашему аккаунту. Войдя в свой профиль на сайте, вы получаете доступ к некоторому набору действий, которые были определены вам разработчиками и администраторами – но не более того

Аутентификация без авторизации возможна, но лишена смысла – система вас узнает, но ничего не даст сделать.  Авторизация без аутентификации также возможна, но только если в системе предусмотрены действия, доступные для анонимных пользователей

Когда речь идет о http-запросах, то аутентификация и авторизация, как правило, совмещены. Внутри запроса (параметров, тела или заголовков) мы передаем сразу как данные аутентификации (например, токен) так и запрос на выполнение некоторых действий из списка тех, которые нам, потенциально, должны быть доступны. В случае, если аутентификация будет пройдена успешна, а авторизация подтвердит наличие прав на выполнение необходимого действия – мы получим необходимый результат

Уровень 2: Схемы Http-аутентификации/авторизации

Рассмотрим основные схемы аутентификации с примерами 

Basic
Самый простой способ аутентификации: в заголовки запроса добавляется заголовок Authorization со значением Basic Логин:Пароль, где пара Логин:Пароль кодированы в Base64. Никакого дополнительного преобразования этих данных не требуется

 

Логин = “bayselonarrend”; Пароль = “14AB22”; Сервер = “exemple.com”; Адрес = “/api”; Заголовки = Новый Соответствие; Basic = Логин + “:” + Пароль; Basic = Base64Строка(ПолучитьДвоичныеДанныеИзСтроки(Basic)); Заголовки.Вставить(“Authorization”, “Basic ” + Basic); НовыйЗапрос = Новый HTTPЗапрос(Адрес, Заголовки); SSL = Новый ЗащищенноеСоединениеOpenSSL; Соединение = Новый HTTPСоединение(Сервер, 443, , , , 3000, SSL); Ответ = Соединение.ВызватьHTTPМетод(“GET”, НовыйЗапрос);

На сегодняшний день такой способ является устаревшим и активно вытесняется, хотя при использовании https считается достаточно безопасным. Никаких хитрых решений для его обработки, в том числе и из 1С, не требуется

 

Digest

Более сложная система. Сервер по запросу отправляет клиенту некоторые данные (nonce), после чего клиент преобразует пару логин-пароль в MD5 хэш-строку на их основе. Практически не используется, так как требует хранения пароля в открытом виде на стороне сервера. Как способ аутентификации конкретно для API встречается еще реже, вероятно из-за необходимости отправки предварительного запроса получения nonce, что в обычной жизни берет на себя браузер
 

Про 1С:
MD5 в 1С реализуется штатными средствами через Новый ХешированиеДанных(ХэшФункция.MD5). Также реализация Digest есть в Коннекторе

Bearer 

Пожалуй самый распространенный способ аутентификации на данный момент. Bearer – это любые, полученные пользователем от провайдера API, данные, которые при передаче в этот API подтверждают его (пользователя) личность. В большинстве случаев это текстовые токены того или иного формата

Authorization: Bearer 17fa874c-cdbf-49e5-88fc-625aa63b3b00

Например, вы регистрируетесь на портале ресурса, который предоставляет публичный API. После входа в некоторый "личный кабинет" при помощи логина и пароля, вам становится доступна функция генерации токена. Вы нажимаете на кнопку, копируете себе сгенерированную строку, после чего передаете её в каждом своем запросе к API. Эта строка – Bearer токен

Про 1С:

В зависимости от реализации API, Bearer можете передаваться в

  • Заголовках запроса Сервер = “exemple.com”; Адрес = “/api”; Токен = “17fa874c-cdbf-49e5-88fc-625aa63b3b00”; Заголовки = Новый Соответствие; Заголовки.Вставить(“Authorization”, “Bearer ” + Токен); НовыйЗапрос = Новый HTTPЗапрос(Адрес, Заголовки); SSL = Новый ЗащищенноеСоединениеOpenSSL; Соединение = Новый HTTPСоединение(Сервер, 443, , , , 3000, SSL); Ответ = Соединение.ВызватьHTTPМетод(“GET”, НовыйЗапрос);
  • В URL – как, например, у Telegram https://api.telegram.org/bot61111111:AAFyzNBOAFfuhAL5GXqbVidwAAAAAAAA/getUpdates | | ——————————————- Вот эта часть
  • В теле запроса Сервер = “exemple.com”; Адрес = “/api”; Токен = “17fa874c-cdbf-49e5-88fc-625aa63b3b00”; Параметры = “?my_token_field=” + Токен; Заголовки = Новый Соответствие; Заголовки.Вставить(“Content-Type”, “application/x-www-form-urlencoded; charset=utf-8”); НовыйЗапрос = Новый HTTPЗапрос(Адрес, Заголовки); НовыйЗапрос.УстановитьТелоИзСтроки(Параметры); SSL = Новый ЗащищенноеСоединениеOpenSSL; Соединение = Новый HTTPСоединение(Сервер, 443, , , , 3000, SSL); Ответ = Соединение.ВызватьHTTPМетод(“POST”, НовыйЗапрос);
  Услуги программирования на 1С. Автоматизация ПП "1С:Бухгалтерия 8 ПРОФ" в ООО "Вектор"

AWS4-HMAC-SHA256

Замороченный способ авторизации AWS и всех сочувствующих – поставщиков S3 в частности. Сводится к генерации авторизационных данных на основе не только лишь токена от провайдера, как в Bearer, но и на основе самого содержимого отправляемого запроса. Сформированные данные отправляются в заголовке Authorization
 

Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request, SignedHeaders=host;range;x-amz-date, Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024 Где: – Строка1 – Наименование метода – Строка2 – Строка <Ваш ID ключа доступа>/<Дата>/<Регион AWS>/<Сервис>/aws4_request – Строка3 – Список передаваемых заголовков – Строка4 – Сигнатура, вычисляемая по одному из доступных алгоритмов

Вычисление сигнатуры – вообще отдельная тема. Пока могу предложить только ссылки на ресурсы Amazon:

  • Про заголовки авторизации
  • Про сигнатуру
     

Для 1С:

AWS – сложный механизм, но благо на 1С уже есть реализации

  • Коннектор умеет в AWS4 авторизацию из коробки
  • REST API AWS S3 Browser – пример интеграции 1С и AWS S3 средствами платформы

OAuth

Стандарт авторизации, чем-то напоминающий AWS. При его использовании, для авторизации необходимо сформировать составную строку из различных данных, после чего передать её в качестве заголовка запроса

Из чего состоит авторизационная строка?

  1. OAuth – наименование стандарта
  2. oauth_consumer_key – секретный ключ от провайдера API
  3. oauth_token – токен от провайдера API
  4. oauth_signature_method – метод создания сигнатуры. Про сигнатуру будет далее.
  5. oauth_timestamp – временная отметка в UNIX Time
  6. oauth_nonce – любой уникальный идентификатор
  7. oauth_version – версия API
  8. oauth_signature – сигнатура

Из представленных выше частей наиболее загадочной является сигнатура. Давайте разбираться, что же это такое
 

Сигнатура – хэш, получаемый путем вычисления базовой строки при помощи метода, указанного в поле oauth_signature_method. Что же является базой сигнатуры?

Это соединение через следующих данных:

  • Вида запроса
  • Целевого URL
  • Всех полей авторизационной строки (из списка выше; в виде Ключ=Значение), но без поля oauth_signature
  • Всех не-двоичных полей основного запроса (в виде Ключ=Значение).

При этом, все поля новой строки (кроме URL и вида запроса) должны быть предварительно отсортированы по алфавиту по названиям (ключам) полей и кодированы в кодировке URL. После соединения строки, она должна быть приведена к верхнему регистру

Т.е. после того, как ’10 плюс Число полей в запросе минус oauth_signature’ пунктов соединены (с кодированием значений в кодировке URL и в расстановке по алфавиту) в одну большую строку вида
 

POST&https%3A%2F%2Fexemple.com&ПОЛЕ1=<ЗНАЧЕНИЕ1>&ПОЛЕ2=<ЗНАЧЕНИЕ2>…OAUTH_CONSUMER_KEY=<КЛЮЧ>&OAUTH_TOKEN=<TOKEN>&OAUTH_TIMESTAMP=<UNIX TIME>&OAUTH_NONCE=<UID>&OAUTH_VERSION=<V>

данная строка прогоняется по алгоритму, указанному в oauth_signature_method. Список доступных алгоритмов определяется провайдером API (как правило, это какой-нибудь один метод). Обычно это алгоритмы HMAC и RSA с SHA хэшированием: HMAC-SHA1, HMAC-SHA256 (H256), RSA-SHA256 (R256) и др. – о них мы поговорим на следующем уровне. После прогона строки через алгоритм, полученное значение переводится в Base64Строка и кодируется в кодировке URL

Услуги по программированию 1С

В результате получается хэш, который и является сигнатурой – его значение дописывается в исходную строку:
 

OAuth oauth_consumer_key=”<ключ>”,oauth_token=”<token>”,oauth_timestamp=”<unix time>”,oauth_nonce=”<uid>”,oauth_version=”<v>”,oauth_signature=”<Наша сигнатура>”

Для создания сигнатуры, алгоритму необходима не только база (наша строка), но и ключ – им может выступать строка <oauth_token> &<oauth_consumer_key>. Так или иначе – подобное будет оговорено в документации к конкретному API

Готовая строка передается в заголовке Authorization

Для 1С:

   Пример создания заголовка  Функция СоздатьЗаголовокАвторизацииV1(Знач Параметры, Знач Поля, Знач ВидЗапроса, Знач URL) ТекущаяДата = OPI_Инструменты.ПолучитьТекущуюДату(); ЗаголовокАвторизации = “”; МетодХэширования = “HMAC-SHA1”; ВерсияАпи = “1.0”; СтрокаСигнатуры = “”; Ключ = “”; OCK = “oauth_consumer_key”; OTK = “oauth_token”; ТекущаяДатаUNIX = OPI_Инструменты.UNIXTime(ТекущаяДата); ТекущаяДатаUNIX = OPI_Инструменты.ЧислоВСтроку(ТекущаяДатаUNIX); ТаблицаПараметров = Новый ТаблицаЗначений; ТаблицаПараметров.Колонки.Добавить(“Ключ”); ТаблицаПараметров.Колонки.Добавить(“Значение”); Для Каждого Поле Из Поля Цикл НоваяСтрока = ТаблицаПараметров.Добавить(); НоваяСтрока.Ключ = Поле.Ключ; НоваяСтрока.Значение = Поле.Значение; КонецЦикла; НоваяСтрока = ТаблицаПараметров.Добавить(); НоваяСтрока.Ключ = OCK; НоваяСтрока.Значение = Параметры[OCK]; НоваяСтрока = ТаблицаПараметров.Добавить(); НоваяСтрока.Ключ = OTK; НоваяСтрока.Значение = Параметры[OTK]; НоваяСтрока = ТаблицаПараметров.Добавить(); НоваяСтрока.Ключ = “oauth_version”; НоваяСтрока.Значение = ВерсияАпи; НоваяСтрока = ТаблицаПараметров.Добавить(); НоваяСтрока.Ключ = “oauth_signature_method”; НоваяСтрока.Значение = МетодХэширования; НоваяСтрока = ТаблицаПараметров.Добавить(); НоваяСтрока.Ключ = “oauth_timestamp”; НоваяСтрока.Значение = ТекущаяДатаUNIX; НоваяСтрока = ТаблицаПараметров.Добавить(); НоваяСтрока.Ключ = “oauth_nonce”; НоваяСтрока.Значение = ТекущаяДатаUNIX; Для Каждого СтрокаТаблицы Из ТаблицаПараметров Цикл СтрокаТаблицы.Ключ = КодироватьСтроку(СтрокаТаблицы.Ключ, СпособКодированияСтроки.КодировкаURL); СтрокаТаблицы.Значение = КодироватьСтроку(СтрокаТаблицы.Значение, СпособКодированияСтроки.КодировкаURL); КонецЦикла; ТаблицаПараметров.Сортировать(“Ключ”); Для Каждого СтрокаТаблицы Из ТаблицаПараметров Цикл СтрокаСигнатуры = СтрокаСигнатуры + СтрокаТаблицы.Ключ + “=” + СтрокаТаблицы.Значение + “&”; КонецЦикла; СтрокаСигнатуры = Лев(СтрокаСигнатуры, СтрДлина(СтрокаСигнатуры) – 1); СтрокаСигнатуры = вРег(ВидЗапроса) + “&” + КодироватьСтроку(URL, СпособКодированияСтроки.КодировкаURL) + “&” + КодироватьСтроку(СтрокаСигнатуры, СпособКодированияСтроки.КодировкаURL); Ключ = КодироватьСтроку(Параметры[“oauth_consumer_secret”], СпособКодированияСтроки.КодировкаURL) + “&” + КодироватьСтроку(Параметры[“oauth_token_secret”], СпособКодированияСтроки.КодировкаURL); Сигнатура = OPI_Криптография.HMAC(ПолучитьДвоичныеДанныеИзСтроки(Ключ) , ПолучитьДвоичныеДанныеИзСтроки(СтрокаСигнатуры) , ХешФункция.SHA1 , 64); Сигнатура = КодироватьСтроку(Base64Строка(Сигнатура), СпособКодированияСтроки.КодировкаURL); Разделитель = “””,”; ЗаголовокАвторизации = ЗаголовокАвторизации + “OAuth ” + “oauth_consumer_key=””” + Параметры[OCK] + Разделитель + “oauth_token=””” + Параметры[OTK] + Разделитель + “oauth_signature_method=””” + МетодХэширования + Разделитель + “oauth_timestamp=””” + ТекущаяДатаUNIX + Разделитель + “oauth_nonce=””” + ТекущаяДатаUNIX + Разделитель + “oauth_version=””” + ВерсияАпи + Разделитель + “oauth_signature=””” + Сигнатура; СоответствиеЗаголовка = Новый Соответствие; СоответствиеЗаголовка.Вставить(“authorization”, ЗаголовокАвторизации); Возврат СоответствиеЗаголовка; КонецФункции

  Услуги программирования на 1С. Автоматизация учета на базе ПП "1С:Розница 8. ПРОФ" в АО "ИПН СТАНКОСТРОЕНИЕ" в облачном сервисе «1С:Предприятие 8 через Интернет» (1cfresh.com)

OAuth2

Сиквел OAuth, более щадящий в вопросах подготовки запроса, но более сложный в организационных вопросах. При его использовании нет необходимости собирать огромную авторизационную строку как у первого OAuth – используется просто обычный токен (т.е. в вопросах непосредственной отправки запроса, OAuth2 – это Bearer)

Основная суть тут уже в получении и обновлении данного токен. Принцип работы таков:
 

  1. Где-то в настройках API "для разработчиков" указывается redirect_url – адрес вашего ресурса, который будет готов принять внешний запрос с токеном. Говоря просто – это должен быть http-сервис с доступом извне. Т.е. без web-сервера ничего не получится
     
  2. Для получения токена, пользователь в браузере переходит на страницу авторизации и нажимает нечто вроде "Войти"
     
  3. После нажатия кнопки, Http-запрос с токеном отправляется по redirect_url. На нем наш обработчик должен забрать эти данные и сохранить
     
  4. В этом запросе есть два токена – обычный access_token (токен доступа), который и используется как раз для необходимых нам запросов и refresh_token (токен обновления), о котором далее
     
  5. Токен доступа (access_token) – не вечен. Время его жизни оговаривается в документации или летит отдельным полем в запросе из п.4 как expire. Для продолжения работы по истечении срока жизни токена, его необходимо обновить. Для этого отправляется особый запрос к API с refresh_token на борту
     
  6. В ответе этого "особого" запроса приходит новый access_token, а у особо поехавших (привет, Twitter) в некоторых случаях – и новый refresh_token. Все это заменяет прошлые данные
     
  7. Пункты 5 и 6 повторяются периодически
      |–> ОбновитьТокен() ->|access_token –> Используется в т-нии n часов для запросов | |refresh_token –| |——–[через n ч.]——————-|

На самом деле, все мы сталкиваемся с подобным механизмом каждый день: "Войти при помощи Google/Yandex/VK ID" на разных сайтах – это реализация OAuth2, где сайт, на котором осуществляется вход, также получает токен нашего аккаунта с определенными правами (они, как правило, показываются на панели входа: "Получит доступ к адресу почты", "Получит доступ к номеру телефона" и все такое)
 

Для 1С:

Пример работы с OAuth и OAuth2 есть в реализации API Twitter в ОПИ

Статья по теме 1

Статья по теме 2

JWT

JWT (JSON Web Tokens) – не стандарт аутентификации, но скорее формат передачи подписанных данных. Готовые токены такого формата могут использоваться для передачи в качестве Bearer 

JWT это JSON данные, состоящие из 3-х частей: заголовка, полезных данных и сигнатуры (подписи) 

  • Заголовок (header) – JSON-объект с информацией о способе вычисления подписи { “alg”: “HS256”, “typ”: “JWT”}
  • Данные (payload) – JSON-объект непосредственно с полезной нагрузкой { “Поле”: “Значение” }
  • Подпись (signature) – значение, вычисляемое следующим способом СекретныйКлюч = “1111”; B64Заголовок = Base64UrlEncode(ПолучитьДвоичныеДанныеИзСтроки(Заголовок)); B64Данные = Base64UrlEncode(ПолучитьДвоичныеДанныеИзСтроки(Данные)); НеподписанныйТокен = B64Заголовок + “.” + B64Данные; Сигнатура = Криптография.HMAC(ПолучитьДвоичныеДанныеИзСтроки(СекретныйКлюч) , ПолучитьДвоичныеДанныеИзСтроки(НеподписанныйТокен) , ХэшФункция.SHA256); Сигнатура = Base64UrlEncode(Сигнатура); … Функция Base64UrlEncode(Знач Значение) Ответ = Base64Строка(Значение); Ответ = СтрРазделить(Ответ, “=”)[0]; Ответ = СтрЗаменить(Ответ, Символы.ВК + Символы.ПС, “”); Ответ = СтрЗаменить(Ответ, “+”, “-“); Ответ = СтрЗаменить(Ответ, “/”, “_”); Возврат Ответ; КонецФункции

Сам токен – соединение всех частей через точку
 

Токен = Заголовок + “.” + Данные + “.” + Сигнатура;

Далее его можно использовать по прямому назначению

Для 1С:
Open-source библиотека для JWT в 1С
Статья по теме
Конструктор JWT на jwt.io

Помимо этих вариантов есть еще схемы: MutualHOBA, VAPID и др., но вероятность встретить подтверждение личности по паспорту в реальной жизни куда выше, чем их реализацию в публичном API. И если некоторые из них (например Mutual) в реальном мире на самом деле существуют, просто под капотом интернета, то найти хотя бы теоретическую информацию о том, что же такое HOBA, вообще крайне сложно
 

  Услуги программирования на 1С. Автоматизация деятельности предприятия на базе программного продукта "1С:Розница 8. Базовая версия" у ИП Шеина А.Е.

Уровень 3 – Алгоритмы

На предыдущем уровне в некоторых из схем нам встречалось создание сигнатур. При работе с каждой из них, в зависимости от воли провайдера API, требуется применение того или иного алгоритма обработки данных. Наиболее распространенными из них на данный момент являются HMAC и RSA, в вариациях, основанных на различных алгоритмах хэширования – HMAC-SHA1, RSA-SHA256 и др. О них и поговорим

HMAC

HMAC (Hash-based message authentication code) – один из самых популярных механизмов проверки целостности информации. Я не буду рассказывать прохладные истории, будто бы понимаю (или должен понимать) как он внутри работает – никому из нас, я думаю это и не нужно. Как и в любых подобных вещах, 95% HMAC – это зубодробительный МАТАН, так что лучше сразу перейдем к 1С

Тут нам повезло гораздо больше (спойлер) чем с алгоритмом RSA: в Библиотеке стандартных подсистем, а именно в модуле РаботаВМоделиСервисаБТС есть замечательная, но, почему-то не экспортная, функция
 

HMAC(Знач Ключ, Знач Данные, Тип, РазмерБлока)

В параметре Тип она принимает значение платформенного перечисления ХешФункция (ХешФункция.SHA1, ХешФункция.SHA256, ХешФункция.SHA512) от которого и будет зависеть, собственно, будет у нас HMAC-SHA256, HMAC-SHA512 или HMAC-SHA1. А большего тут и не надо

RSA

RSA используется не так часто, как HMAC, но зато печально известно в мире 1С своим применением в сервисах от Google, когда речь заходит о работе через Service account. В 1С нет реализации RSA и лично я встречал всего несколько вариантов решений данной проблемы:

  • В репозитории malikov-pro/google_api_1c можно найти подобное решение
      Функция ПолучиьПодписьRSASHA256(СтрокаДанные, КлючПодписи_XML) Экспорт Хеширование = Новый ХешированиеДанных(ХешФункция.SHA256); Хеширование.Добавить(СтрокаДанные); ХешДвоичный = Хеширование.ХешСумма; КриптоПровайдер = Новый COMОбъект(“System.Security.Cryptography.RSACryptoServiceProvider”); КриптоПровайдер.FromXmlString(КлючПодписи_XML); SafeArrayBinХешДляПодписи = SafeИзДвоичных(ХешДвоичный); SafeArrayBinПодписьДвоичная = КриптоПровайдер.SignHash(SafeArrayBinХешДляПодписи, “SHA256”); Возврат ДвоичныеИзSafe(SafeArrayBinПодписьДвоичная); КонецФункции

    Тут используется COM объект, что не очень хорошо, но выглядит довольно просто и под Windows будет работать

  • Dll на Инфостарте. Скажу честно – не проверял

  • Формирование объекта через новый платформенный объект ТокенДоступа. Есть недавняя статья на эту тему, но подойдет только тем, у кого свежая версия 1С (в статье 8.3.21) 

  • Есть пример решения на Python, интегрированного в решение для Google Sheets на 1С от Евгения Комиссарова

Возможно есть и другие, но я о них не знаю. Хотя очень искал, когда начинал делать интеграцию с Google Drive

Другие же алгоритмы на практике встречаются гораздо реже

В заключении

В принципе, это все, что я хотел рассказать. Если я что-то упустил или вы нашли ошибку – напишите в комментариях. Также ниже будет список полезных ресурсов, который могут помочь при работе с API. Список будет дополняться, так что если знаете еще что-нибудь – об этом можете написать также
 

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

  • Коннектор: удобный HTTP-клиент для 1С:Предприятие 8
  • 1С-JWT от pintov
  • Внешня компонента для работы с RabbitMQ по http
  • Открытй пакет интеграций для различных популярных API

 Мой GitHub: https://gitub.com/Bayselonarrend Лицензия MIT: https://mit-license.org

Услуги программирования на 1С представляют собой комплекс деятельности, направленной на создание, настройку, модификацию и поддержку программных решений на платформе 1С:Предприятие. 1С — это популярная российская система автоматизации бизнес-процессов, используемая предприятиями различных отраслей.

В контексте услуг программирования на 1С специалисты создают программные продукты, которые автоматизируют различные аспекты деятельности предприятия, такие как учет, финансы, управление персоналом, складское хозяйство и т.д. Эти продукты могут быть как стандартными, так и индивидуально разработанными под конкретные потребности заказчика.

Услуги программирования на 1С часто предоставляются специализированными IT-компаниями или независимыми разработчиками, обладающими высокой квалификацией и опытом работы с данной платформой. Эти услуги помогают компаниям оптимизировать бизнес-процессы, повысить эффективность работы и снизить затраты на управление и ведение бизнеса.

Добавить комментарий