1
0
mirror of https://github.com/Bayselonarrend/OpenIntegrations.git synced 2025-11-23 22:05:15 +02:00

Функция создания токена для Service аккаунта Google

This commit is contained in:
Anton Titovets
2025-05-08 19:15:01 +03:00
parent 88704f7664
commit cb09dc4d67
17 changed files with 249 additions and 55 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -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"

View File

@@ -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

View File

@@ -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<Vec<u8>, String> {
// ===== RSA ===== //
fn load_rsa_key(key: &[u8]) -> Result<RsaPrivateKey, String> {
pub fn load_rsa_key(key_data: &[u8]) -> Result<RsaPrivateKey, String> {
if let Ok(key) = RsaPrivateKey::from_pkcs1_der(key_data) {
return Ok(key);
}
match RsaPrivateKey::from_pkcs1_der(key){
Ok(v) => Ok(v),
Err(_) => {
if let Ok(key) = RsaPrivateKey::from_pkcs8_der(key_data) {
return Ok(key);
}
let pem_string = match std::str::from_utf8(key){
Ok(pem_str) => pem_str,
Err(_) => return Err("Invalid RSA key format".to_string()),
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()),
};
match RsaPrivateKey::from_pkcs1_pem(pem_string){
Ok(v) => Ok(v),
Err(_) => Err("Invalid RSA key format".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<D>(key: &RsaPrivateKey, data: &[u8]) -> Result<Vec<u8>, String>
where
D: Digest + FixedOutput,
SigningKey<D>: From<RsaPrivateKey>,
D: Digest + FixedOutput + rsa::pkcs8::AssociatedOid,
{
let signing_key = SigningKey::<D>::new_unprefixed(key.clone());
// Создаем схему подписи с указанием хеш-алгоритма
let scheme = Pkcs1v15Sign::new::<D>();
// Хешируем данные
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<Vec<u8>, String> {
let key = load_rsa_key(key)?;

Binary file not shown.

Binary file not shown.

View File

@@ -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, СтруктураТела, , Ложь);
Возврат Ответ;
КонецФункции
#КонецОбласти
#Область СлужебныйПрограммныйИнтерфейс

View File

@@ -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];
Вывод = СтрЗаменить(Вывод, Символы.ВК + Символы.ПС, "");
Вывод = СтрЗаменить(Вывод, "+", "-");
Вывод = СтрЗаменить(Вывод, "/", "_");
Возврат Вывод;
КонецФункции
#КонецОбласти
#КонецОбласти

View File

@@ -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

View File

@@ -1104,6 +1104,7 @@
ДобавитьЛог("ОбработатьЗапрос: перенос тела в объект HTTPЗапроса");
Если УстановитьТелоЗапроса().Ошибка Тогда Возврат ЭтотОбъект; КонецЕсли;
ГарантироватьТелоКоллекцию();
ДополнитьЗаголовки();
Если ВыполнитьСразу Тогда
@@ -2430,18 +2431,6 @@
ТаблицаПараметров.Колонки.Добавить("Ключ");
ТаблицаПараметров.Колонки.Добавить("Значение");
Если Не ЗначениеЗаполнено(ЗапросТелоКоллекция)
Или Не OPI_Инструменты.ЭтоКоллекция(ЗапросТелоКоллекция, Истина) Тогда
Попытка
ЗапросТелоКоллекция = ЗапросТело;
OPI_ПреобразованиеТипов.ПолучитьКоллекциюКлючИЗначение(ЗапросТелоКоллекция);
Исключение
ЗапросТелоКоллекция = Новый Структура;
КонецПопытки;
КонецЕсли;
Если ПолучитьНастройку("MultipartВOAuth") Или Не Multipart Тогда
Для Каждого Поле Из ЗапросТелоКоллекция Цикл
@@ -2630,6 +2619,24 @@
КонецПроцедуры
Процедура ГарантироватьТелоКоллекцию()
Если Не ЗначениеЗаполнено(ЗапросТелоКоллекция)
Или Не OPI_Инструменты.ЭтоКоллекция(ЗапросТелоКоллекция, Истина) Тогда
Попытка
ЗапросТелоКоллекция = ЗапросТело;
OPI_ПреобразованиеТипов.ПолучитьКоллекциюКлючИЗначение(ЗапросТелоКоллекция);
Исключение
ЗапросТелоКоллекция = Новый Структура;
КонецПопытки;
ЗапросТелоКоллекция = ?(ЗначениеЗаполнено(ЗапросТелоКоллекция), ЗапросТелоКоллекция, Новый Структура);
КонецЕсли;
КонецПроцедуры
#КонецОбласти
#КонецОбласти