diff --git a/data.json.gpg b/data.json.gpg index 757a15332f..362395c5d2 100644 Binary files a/data.json.gpg and b/data.json.gpg differ diff --git a/docs/docusaurus/static/img/Docs/GoogleCalendar/13.png b/docs/docusaurus/static/img/Docs/GoogleCalendar/13.png new file mode 100644 index 0000000000..55c1b2692d Binary files /dev/null and b/docs/docusaurus/static/img/Docs/GoogleCalendar/13.png differ diff --git a/docs/docusaurus/static/img/Docs/GoogleCalendar/14.png b/docs/docusaurus/static/img/Docs/GoogleCalendar/14.png new file mode 100644 index 0000000000..ab85de2413 Binary files /dev/null and b/docs/docusaurus/static/img/Docs/GoogleCalendar/14.png differ diff --git a/docs/docusaurus/static/img/Docs/GoogleCalendar/15.png b/docs/docusaurus/static/img/Docs/GoogleCalendar/15.png new file mode 100644 index 0000000000..f5b9c9a011 Binary files /dev/null and b/docs/docusaurus/static/img/Docs/GoogleCalendar/15.png differ diff --git a/docs/docusaurus/static/img/Docs/GoogleCalendar/16.png b/docs/docusaurus/static/img/Docs/GoogleCalendar/16.png new file mode 100644 index 0000000000..647f3af2c9 Binary files /dev/null and b/docs/docusaurus/static/img/Docs/GoogleCalendar/16.png differ diff --git a/docs/docusaurus/static/img/Docs/GoogleCalendar/17.png b/docs/docusaurus/static/img/Docs/GoogleCalendar/17.png new file mode 100644 index 0000000000..7dcf09495b Binary files /dev/null and b/docs/docusaurus/static/img/Docs/GoogleCalendar/17.png differ diff --git a/src/addins/crypto/Cargo.toml b/src/addins/crypto/Cargo.toml index d5473d606a..f024f4c9e3 100644 --- a/src/addins/crypto/Cargo.toml +++ b/src/addins/crypto/Cargo.toml @@ -16,7 +16,7 @@ opt-level = "z" [dependencies] addin1c = "0.5.0" hmac = "0.12" -sha1 = "0.10" -sha2 = "0.10" +sha1 = { version = "0.10", features = ["oid"] } +sha2 = { version = "0.10", features = ["oid"] } rsa = "0.9" -digest = "0.10.7" +digest = "0.10.7" \ No newline at end of file diff --git a/src/addins/crypto/dependencies.log b/src/addins/crypto/dependencies.log index ef73fd6ec3..e33a59b8a1 100644 --- a/src/addins/crypto/dependencies.log +++ b/src/addins/crypto/dependencies.log @@ -1,9 +1,9 @@ "MAIN ---" - linux-vdso.so.1 (0x00007ffd2bba8000) - libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f8e3f32c000) - libc.so.6 => /lib64/libc.so.6 (0x00007f8e3ef55000) - libdl.so.2 => /lib64/libdl.so.2 (0x00007f8e3ed51000) - /lib64/ld-linux-x86-64.so.2 (0x00007f8e3f54c000) + linux-vdso.so.1 (0x00007fff64dcd000) + libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f075273c000) + libc.so.6 => /lib64/libc.so.6 (0x00007f0752365000) + libdl.so.2 => /lib64/libdl.so.2 (0x00007f0752161000) + /lib64/ld-linux-x86-64.so.2 (0x00007f075295c000) GLIBC_2.2.5 GLIBC_2.3 GLIBC_2.14 diff --git a/src/addins/crypto/src/component/methods.rs b/src/addins/crypto/src/component/methods.rs index 1cfba6a23e..1e921554d0 100644 --- a/src/addins/crypto/src/component/methods.rs +++ b/src/addins/crypto/src/component/methods.rs @@ -1,10 +1,9 @@ use hmac::{Hmac, Mac}; use sha1::Sha1; -use sha2::{Sha256}; -use rsa::{RsaPrivateKey, pkcs1::DecodeRsaPrivateKey}; -use rsa::pkcs1v15::SigningKey; +use sha2::Sha256; +use rsa::{RsaPrivateKey, pkcs1::DecodeRsaPrivateKey, pkcs8::DecodePrivateKey}; +use rsa::pkcs1v15::{Pkcs1v15Sign}; use rsa::signature::digest::{Digest, FixedOutput}; -use rsa::signature::{Signer, SignatureEncoding}; // ===== HMAC ===== // @@ -30,45 +29,49 @@ pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result, String> { // ===== RSA ===== // -fn load_rsa_key(key: &[u8]) -> Result { - - match RsaPrivateKey::from_pkcs1_der(key){ - Ok(v) => Ok(v), - Err(_) => { - - let pem_string = match std::str::from_utf8(key){ - Ok(pem_str) => pem_str, - Err(_) => return Err("Invalid RSA key format".to_string()), - }; - - match RsaPrivateKey::from_pkcs1_pem(pem_string){ - Ok(v) => Ok(v), - Err(_) => Err("Invalid RSA key format".to_string()), - } - - } +pub fn load_rsa_key(key_data: &[u8]) -> Result { + if let Ok(key) = RsaPrivateKey::from_pkcs1_der(key_data) { + return Ok(key); } + + if let Ok(key) = RsaPrivateKey::from_pkcs8_der(key_data) { + return Ok(key); + } + + let pem_str = match std::str::from_utf8(key_data) { + Ok(s) => s, + Err(_) => return Err("Invalid key format: not DER or PEM".to_string()), + }; + + if let Ok(key) = RsaPrivateKey::from_pkcs1_pem(pem_str) { + return Ok(key); + } + + if let Ok(key) = RsaPrivateKey::from_pkcs8_pem(pem_str) { + return Ok(key); + } + + Err("Invalid RSA key format. Expected: PKCS#1/8 PEM or DER".to_string()) } fn rsa_sign(key: &RsaPrivateKey, data: &[u8]) -> Result, String> where - D: Digest + FixedOutput, - SigningKey: From, + D: Digest + FixedOutput + rsa::pkcs8::AssociatedOid, { - let signing_key = SigningKey::::new_unprefixed(key.clone()); + // Создаем схему подписи с указанием хеш-алгоритма + let scheme = Pkcs1v15Sign::new::(); + + // Хешируем данные let mut hasher = D::new(); Digest::update(&mut hasher, data); let digest = hasher.finalize(); - let sign_result = signing_key.try_sign(&digest); + // Подписываем хеш + let signature = key.sign(scheme, &digest).map_err(|e| format!("RSA signing error: {}", e))?; - match sign_result { - Ok(signature) => Ok(signature.to_vec()), - Err(e) => Err(format!("RSA signing error: {}", e)), - } + Ok(signature) } - /// Подписывает данные с помощью RSA-SHA1 pub fn rsa_sha1(key: &[u8], data: &[u8]) -> Result, String> { let key = load_rsa_key(key)?; @@ -79,4 +82,4 @@ pub fn rsa_sha1(key: &[u8], data: &[u8]) -> Result, String> { pub fn rsa_sha256(key: &[u8], data: &[u8]) -> Result, String> { let key = load_rsa_key(key)?; rsa_sign::(&key, data) -} +} \ No newline at end of file diff --git a/src/en/OInt/addins/OPI_Cryptography.zip b/src/en/OInt/addins/OPI_Cryptography.zip index 5d67b83570..ac91d6b703 100644 Binary files a/src/en/OInt/addins/OPI_Cryptography.zip and b/src/en/OInt/addins/OPI_Cryptography.zip differ diff --git a/src/en/OPI/src/CommonTemplates/OPI_Cryptography/Template.addin b/src/en/OPI/src/CommonTemplates/OPI_Cryptography/Template.addin index 5d67b83570..ac91d6b703 100644 Binary files a/src/en/OPI/src/CommonTemplates/OPI_Cryptography/Template.addin and b/src/en/OPI/src/CommonTemplates/OPI_Cryptography/Template.addin differ diff --git a/src/ru/OInt/addins/OPI_Cryptography.zip b/src/ru/OInt/addins/OPI_Cryptography.zip index 5d67b83570..ac91d6b703 100644 Binary files a/src/ru/OInt/addins/OPI_Cryptography.zip and b/src/ru/OInt/addins/OPI_Cryptography.zip differ diff --git a/src/ru/OPI/src/CommonModules/OPI_GoogleWorkspace/Module.bsl b/src/ru/OPI/src/CommonModules/OPI_GoogleWorkspace/Module.bsl index 630eb4bb22..39d435f080 100644 --- a/src/ru/OPI/src/CommonModules/OPI_GoogleWorkspace/Module.bsl +++ b/src/ru/OPI/src/CommonModules/OPI_GoogleWorkspace/Module.bsl @@ -1,4 +1,4 @@ -// OneScript: ./OInt/core/Modules/OPI_GoogleWorkspace.os +// OneScript: ./OInt/core/Modules/OPI_GoogleWorkspace.os // Lib: Google Workspace // CLI: google @@ -136,6 +136,73 @@ КонецФункции +// Получить токен service аккаунта +// Получает токен авторизации по данным service аккаунта +// +// Примечание: +// Список доступных областей действия: [developers.google.com](https://developers.google.com/identity/protocols/oauth2/scopes) +// +// Параметры: +// Данные - Произвольный - JSON данные авторизации как файл, коллекция или двоичные данные - auth +// ОбластиДействия - Массив Из Строка - Область действия (scope) или массив областей - scope +// ВремяЖизни - Число - Время жизни токена в секундах - exp +// +// Возвращаемое значение: +// Соответствие Из КлючИЗначение - сериализованный JSON ответа от Google +Функция ПолучитьТокенServiceАккаунта(Знач Данные, Знач ОбластиДействия, Знач ВремяЖизни = 3600) Экспорт + + ТекстОшибки = "Переданные данные service аккаунта не являются валидным JSON"; + OPI_ПреобразованиеТипов.ПолучитьКоллекциюКлючИЗначение(Данные, ТекстОшибки); + OPI_ПреобразованиеТипов.ПолучитьЧисло(ВремяЖизни); + OPI_ПреобразованиеТипов.ПолучитьМассив(ОбластиДействия); + + МассивОбязательныхПолей = Новый Массив; + МассивОбязательныхПолей.Добавить("token_uri"); + МассивОбязательныхПолей.Добавить("client_email"); + МассивОбязательныхПолей.Добавить("private_key"); + МассивОбязательныхПолей.Добавить("private_key_id"); + + ОтсутствующиеПоля = OPI_Инструменты.НайтиОтсутствующиеПоляКоллекции(Данные, МассивОбязательныхПолей); + + Если ЗначениеЗаполнено(ОтсутствующиеПоля) Тогда + + ШаблонОшибкиПолей = "В данных service аккаунта отсутствуют обязательные поля: %1"; + ТекстОшибкиПолей = СтрШаблон(ШаблонОшибкиПолей, СтрСоединить(ОтсутствующиеПоля, ", ")); + ВызватьИсключение ТекстОшибкиПолей; + + КонецЕсли; + + ОбластиДействияСтрокой = СтрСоединить(ОбластиДействия, " "); + КлючПодписи = Данные["private_key"]; + URL = Данные["token_uri"]; + + ТекущаяДата = ТекущаяУниверсальнаяДата(); + ДатаСгорания = ТекущаяДата + ВремяЖизни; + + UnixTime = OPI_Инструменты.UnixTime(ТекущаяДата); + ExpTime = OPI_Инструменты.UnixTime(ДатаСгорания); + + Payload = Новый Структура; + + Payload.Вставить("iss" , Данные["client_email"]); + Payload.Вставить("scope", ОбластиДействияСтрокой); + Payload.Вставить("aud" , URL); + Payload.Вставить("exp" , Число(ExpTime)); + Payload.Вставить("iat" , Число(UnixTime)); + + ДопЗаголовки = Новый Структура("kid", Данные["private_key_id"]); + + JWT = OPI_Криптография.JWT(Payload, КлючПодписи, "RS256", ДопЗаголовки); + + Грант = "urn:ietf:params:oauth:grant-type:jwt-bearer"; + СтруктураТела = Новый Структура("grant_type,assertion", Грант, JWT); + + Ответ = OPI_ЗапросыHTTP.PostСТелом(URL, СтруктураТела, , Ложь); + + Возврат Ответ; + +КонецФункции + #КонецОбласти #Область СлужебныйПрограммныйИнтерфейс diff --git a/src/ru/OPI/src/CommonModules/OPI_Криптография/Module.bsl b/src/ru/OPI/src/CommonModules/OPI_Криптография/Module.bsl index f45d86b79f..812098be7a 100644 --- a/src/ru/OPI/src/CommonModules/OPI_Криптография/Module.bsl +++ b/src/ru/OPI/src/CommonModules/OPI_Криптография/Module.bsl @@ -1,4 +1,4 @@ -// OneScript: ./OInt/tools/Modules/internal/Modules/OPI_Криптография.os +// OneScript: ./OInt/tools/Modules/internal/Modules/OPI_Криптография.os // MIT License @@ -76,8 +76,62 @@ КонецФункции +Функция JWT(Знач Payload, Знач КлючПодписи, Знач Метод, Знач ДопЗаголовки = "") Экспорт + + OPI_ПреобразованиеТипов.ПолучитьСтроку(Метод); + OPI_ПреобразованиеТипов.ПолучитьКоллекциюКлючИЗначение(Payload); + OPI_ПреобразованиеТипов.ПолучитьДвоичныеДанные(КлючПодписи, Истина, Ложь); + + Метод = вРег(Метод); + + Если Метод = "HS256" Тогда + + Алгоритм = "HMAC"; + ФункцияХеша = "SHA256"; + + ИначеЕсли Метод = "RS256" Тогда + + Алгоритм = "RSA"; + ФункцияХеша = "SHA256"; + + Иначе + ВызватьИсключение "JWT: Неподдерживаемый метод"; + КонецЕсли; + + Заголовки = Новый Структура("alg,typ", Метод, "JWT"); + + Если ЗначениеЗаполнено(ДопЗаголовки) Тогда + + OPI_ПреобразованиеТипов.ПолучитьКоллекциюКлючИЗначение(ДопЗаголовки); + + Для Каждого КлючЗначение Из ДопЗаголовки Цикл + Заголовки.Вставить(КлючЗначение.Ключ, КлючЗначение.Значение); + КонецЦикла; + + КонецЕсли; + + PayloadСтрокой = OPI_Инструменты.JSONСтрокой(Payload, , Ложь); + ЗаголовкиСтркой = OPI_Инструменты.JSONСтрокой(Заголовки, , Ложь); + + PayloadДвоичные = ПолучитьДвоичныеДанныеИзСтроки(PayloadСтрокой); + ЗаголовкиДвоичные = ПолучитьДвоичныеДанныеИзСтроки(ЗаголовкиСтркой); + + PayloadBase64 = Base64UrlEncode(PayloadДвоичные); + ЗаголовкиBase64 = Base64UrlEncode(ЗаголовкиДвоичные); + + Токен = СтрШаблон("%1.%2", ЗаголовкиBase64, PayloadBase64); + ТокенДвоичные = ПолучитьДвоичныеДанныеИзСтроки(Токен); + + Подпись = СоздатьПодпись(КлючПодписи, ТокенДвоичные, Алгоритм, ФункцияХеша); + ПодписьBase64 = Base64UrlEncode(Подпись); + + Токен = СтрШаблон("%1.%2", Токен, ПодписьBase64); + + Возврат Токен; + +КонецФункции -#Область БСП +#Область Заимстованные /////////////////////////////////////////////////////////////////////////////////////////////////////// // Copyright (c) 2019, ООО 1С-Софт @@ -148,6 +202,41 @@ КонецФункции +// The MIT License (MIT) +// +// Copyright (c) 2017 Vasily Pintov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// https://github.com/pintov/1c-jwt + +Функция Base64UrlEncode(Знач Значение) + + Вывод = Base64Строка(Значение); + Вывод = СтрРазделить(Вывод, "=")[0]; + Вывод = СтрЗаменить(Вывод, Символы.ВК + Символы.ПС, ""); + Вывод = СтрЗаменить(Вывод, "+", "-"); + Вывод = СтрЗаменить(Вывод, "/", "_"); + Возврат Вывод; + +КонецФункции + #КонецОбласти #КонецОбласти diff --git a/src/ru/OPI/src/CommonModules/OPI_Тесты/Module.bsl b/src/ru/OPI/src/CommonModules/OPI_Тесты/Module.bsl index 3cc3f549bc..ab38a6af93 100644 --- a/src/ru/OPI/src/CommonModules/OPI_Тесты/Module.bsl +++ b/src/ru/OPI/src/CommonModules/OPI_Тесты/Module.bsl @@ -841,10 +841,13 @@ OPI_ПолучениеДанныхТестов.ПараметрВКоллекцию("Google_ClientSecret", ПараметрыТеста); OPI_ПолучениеДанныхТестов.ПараметрВКоллекцию("Google_Code" , ПараметрыТеста); OPI_ПолучениеДанныхТестов.ПараметрВКоллекцию("Google_Refresh" , ПараметрыТеста); + OPI_ПолучениеДанныхТестов.ПараметрВКоллекцию("Google_ServiceData" , ПараметрыТеста); + OPI_ПолучениеДанныхТестов.ПараметрВКоллекцию("Access_Token" , ПараметрыТеста); GoogleWorkspace_СформироватьСсылкуПолученияКода(ПараметрыТеста); GoogleWorkspace_ПолучитьТокенПоКоду(ПараметрыТеста); GoogleWorkspace_ОбновитьТокен(ПараметрыТеста); + GoogleWorkspace_ПолучитьТокенServiceАккаунта(ПараметрыТеста); КонецПроцедуры @@ -5416,13 +5419,38 @@ // END OPI_ПолучениеДанныхТестов.Проверка_ГуглТокен(Результат); - OPI_ПолучениеДанныхТестов.ЗаписатьПараметр("Google_Token", Результат["access_token"]); OPI_Инструменты.Пауза(5); КонецПроцедуры +Процедура GoogleWorkspace_ПолучитьТокенServiceАккаунта(ПараметрыФункции) + + Данные = ПараметрыФункции["Google_ServiceData"]; // URL, двоичные данные, файл или коллекция + + Токен = ПараметрыФункции["Access_Token"]; // SKIP + Данные = OPI_ЗапросыHTTP // SKIP + .НовыйЗапрос() // SKIP + .Инициализировать(Данные) // SKIP + .ДобавитьBearerАвторизацию(Токен) // SKIP + .ОбработатьЗапрос("GET") // SKIP + .ВернутьОтветКакДвоичныеДанные(); // SKIP + + ОбластиДействия = Новый Массив; + ОбластиДействия.Добавить("https://www.googleapis.com/auth/calendar"); + ОбластиДействия.Добавить("https://www.googleapis.com/auth/drive"); + ОбластиДействия.Добавить("https://www.googleapis.com/auth/spreadsheets"); + + Результат = OPI_GoogleWorkspace.ПолучитьТокенServiceАккаунта(Данные, ОбластиДействия); + + // END + + OPI_ПолучениеДанныхТестов.Проверка_ГуглТокен(Результат); + OPI_ПолучениеДанныхТестов.ЗаписатьПараметр("Google_ServiceToken", Результат["access_token"]); + +КонецПроцедуры + #КонецОбласти #Область GoogleCalendar diff --git a/src/ru/OPI/src/CommonTemplates/OPI_Cryptography/Template.addin b/src/ru/OPI/src/CommonTemplates/OPI_Cryptography/Template.addin index 5d67b83570..ac91d6b703 100644 Binary files a/src/ru/OPI/src/CommonTemplates/OPI_Cryptography/Template.addin and b/src/ru/OPI/src/CommonTemplates/OPI_Cryptography/Template.addin differ diff --git a/src/ru/OPI/src/DataProcessors/OPI_HTTPКлиент/ObjectModule.bsl b/src/ru/OPI/src/DataProcessors/OPI_HTTPКлиент/ObjectModule.bsl index 66fd8709b7..95c0dcd825 100644 --- a/src/ru/OPI/src/DataProcessors/OPI_HTTPКлиент/ObjectModule.bsl +++ b/src/ru/OPI/src/DataProcessors/OPI_HTTPКлиент/ObjectModule.bsl @@ -1104,6 +1104,7 @@ ДобавитьЛог("ОбработатьЗапрос: перенос тела в объект HTTPЗапроса"); Если УстановитьТелоЗапроса().Ошибка Тогда Возврат ЭтотОбъект; КонецЕсли; + ГарантироватьТелоКоллекцию(); ДополнитьЗаголовки(); Если ВыполнитьСразу Тогда @@ -2430,18 +2431,6 @@ ТаблицаПараметров.Колонки.Добавить("Ключ"); ТаблицаПараметров.Колонки.Добавить("Значение"); - Если Не ЗначениеЗаполнено(ЗапросТелоКоллекция) - Или Не OPI_Инструменты.ЭтоКоллекция(ЗапросТелоКоллекция, Истина) Тогда - - Попытка - ЗапросТелоКоллекция = ЗапросТело; - OPI_ПреобразованиеТипов.ПолучитьКоллекциюКлючИЗначение(ЗапросТелоКоллекция); - Исключение - ЗапросТелоКоллекция = Новый Структура; - КонецПопытки; - - КонецЕсли; - Если ПолучитьНастройку("MultipartВOAuth") Или Не Multipart Тогда Для Каждого Поле Из ЗапросТелоКоллекция Цикл @@ -2630,6 +2619,24 @@ КонецПроцедуры +Процедура ГарантироватьТелоКоллекцию() + + Если Не ЗначениеЗаполнено(ЗапросТелоКоллекция) + Или Не OPI_Инструменты.ЭтоКоллекция(ЗапросТелоКоллекция, Истина) Тогда + + Попытка + ЗапросТелоКоллекция = ЗапросТело; + OPI_ПреобразованиеТипов.ПолучитьКоллекциюКлючИЗначение(ЗапросТелоКоллекция); + Исключение + ЗапросТелоКоллекция = Новый Структура; + КонецПопытки; + + ЗапросТелоКоллекция = ?(ЗначениеЗаполнено(ЗапросТелоКоллекция), ЗапросТелоКоллекция, Новый Структура); + + КонецЕсли; + +КонецПроцедуры + #КонецОбласти #КонецОбласти