Авторизация OAuth 2.0 в API Google и получение access token для сервисной учетной записи, не требует подтверждения пользователя

Обработка получает google api access token методом two-legged OAuth (2LO), это вариант сервер-сервер с цифровой подписью, когда не требуется подтверждение пользователя. Заполняет гугл-таблицу через API. При ее работе также происходит парсинг закрытого RSA-ключа PEM, результат парса выводится в форму.

Пока в интернете я встречал только описание использования из 1С "Трехногого OAUth" (three-legged OAuth), где вызов API идет от имени конечного пользователя и обычно требуется подтверждение пользователя. Способ на мой взгляд длинноватый, поэтому я решил вызвать из 1Ски по-человечески двуногую авторизацию: "two-legged OAuth," или "2LO". Делал на платформе 8.3.10.2168, протестировал также на 8.3.11.3034. Добавлено 17.07.2024: вариант обработки для платформы 8.3.9 (проверено на 8.3.9.2033 и 8.3.9.2233)

Речь идет о модели авторизации, описанной:
краткое описание с картинкой — https://developers.google.com/identity/protocols/OAuth2#serviceaccount
подробное — https://developers.google.com/identity/protocols/OAuth2ServiceAccount

Этот способ требует создания и криптографического подписывания JSON Web Tokens (JWT). На данный момент встроенным Менеджером Криптографии 1ска не умеет подписывать SHA256 хеш, а гугл другого не приемлет. Изначально я использовал COM-объект System.Security.Cryptography.RSACryptoServiceProvider. Он работал как надо, но смущало, что в документации не написано, что он так умеет (в опциях метода SignHash отсутствует вариант SHA256). Для гарантии, что будет работать на другом компьютере, я решил вычислить цифровую подпись "вручную" обычной арифметикой, которую достойно выдержала 1С. Примечательно, что при этом она оперирует десятичными 309-значными числами, и подпись вычисляет за пару секунд. Триста девять — это не опечатка, вот например, значение одной из переменных типа Число, используемых при вычислении:

502 980 391 062 131 532 641 704 729 356 208 435 540 985 711 615 837 230 131 224 218 031 451 499 103 580 000 500 
042 512 824 109 317 254 211 102 144 888 404 960 708 248 565 484 714 907 166 675 270 791 229 966 881 267 979 216 
170 431 549 149 990 776 636 979 550 241 423 110 985 244 106 352 654 087 401 423 069 254 226 452 760 694 752 994 
636 816 447 756 286 216 072 699 194 804 391 178 250 315 913 588 444 283 437

На 8.3.9 вычисляет дольше, но тоже за приемлемое время — похоже, используются не все ядра процессора.

Обработка получает токен от имени сервисной учетной записи моего тестового проекта. Scope запрашивается только для API spreadsheets. Для демонстрации работоспособности полученного токена, из формы обработки можно изменить столбец тестовой гугл-таблицы, а затем по указанному на форме обработки адресу посмотреть результат.

Для получения токенов на api spreadsheets от имени Вашего сервис-аккаунта, необходимо в консоли разработчика https://console.developers.google.com создать/скачать JSON-ключ сервисного аккаунта с доступом к api spreadsheets, и указать его в поле обработки "Путь файла ключа json". Токен своей учетки Вы получите, но при попытке изменить мою тестовую таблицу с таким токеном гугл вернет error 403 "PERMISSION_DENIED", поскольку у Вашего сервис-аккаунта нет доступа к моей таблице.

При воспроизведении в своей работе аналога продемонстрированного механизма не забудьте прописать нужные Вам "scope" в коде обработки и соответствующие API при запросе у гугла json-ключа. Также не забудьте расшарить доступ к объектам гугла, которые Вы хотите изменять или читать через API. Доступ нужно предоставлять емейлу Вашей сервисной учетной записи. Этот емейл можно найти внутри json ключа, в значении свойства "client_email", либо в моей обработке указать путь к Вашему JSON-ключу и нажать кнопку "разобрать сертификат". Емейл отобразится в поле "Расшарить для емейла".

Важный момент: в операционной системе должно быть правильное время и правильный часовой пояс (используется функция 1с УниверсальноеВремя), иначе обработка запросит у гугла неверные дату начала и окончания срока действия токена, как следствие токен не выдадут, в ответе будет что-то со словом time. Если правильные системное время и пояс устанавливать не хотите — тогда нужно в модуле формы указать свой алгоритм расчета времени для переменной ВремяИстеченияUTC. Учитывайте, что в данной схеме авторизации гугл выдает токены максимум на час, затем нужно получать новый токен.

Для разбирающихся в криптографии. Цифровая подпись арифметически вычисляется с использованием SHA256withRSA (RSASSA-PKCS1-V1_5-SIGN with the SHA-256 hash function). Для этого в процессе обработки средствами 1С происходит парсинг закрытого RSA-ключа в формате PEM, BASE64 которого содержится в сертификате JSON. На вкладке "Парс сертификата json" в дерево выводится результат парсинга, и в ветке дерева "OCTET STRING" содержится SEQUENCE, в котором перечислены INTEGER — параметры закрытого и открытого ключей: Version, Modulus, publicExponent (Exponent, E), privateExponent (D), prime1 (P), prime2 (Q), exponent1 (dP или d mod (p-1)), exponent2 (dQ или d mod (q-1)), coefficient (InverseQ или InvQ или (inverse of q) mod p).

54 Comments

  1. uginx

    супер! все работает

    Reply
  2. uginx

    а не подскажете как добавлять строку в конец листа?

    у меня на такую строку

    /v4/spreadsheets/1u3hBxBn0hHfOOyJ6TjLzsji58SPP3ttNyRN31xTgUk­E/values/Sheet2:append?valueInputOption=USER_ENTERED

    ругается с кодом 404

    а так

    /v4/spreadsheets/1u3hBxBn0hHfOOyJ6TjLzsji58SPP3ttNyRN31xTgUk­E/values/Sheet2?valueInputOption=USER_ENTERED

    работает, но записывает с начала листа

    Reply
  3. uno-c

    Здравствуйте. Если нужно добавить новую дополнительную пустую строку (т.е. на странице последняя строка, в которую можно что-то записать была 969я, нужно, чтоб появилась 970я строка), то нужно использовать POST по адресу https://sheets.googleapis.com/v4/spreadsheets/spreadsheetId:batchUpdate, при этом в теле нужно передать JSON {«requests»: [ { «appendDimension»: { «sheetId»: sheetId, «dimension»: «ROWS», «length»:1}}]} https://developers.google.com/sheets/api/samples/rowcolumn#append_empty_rows_or_columns

    Reply
  4. uginx

    (3) спасибо! поменял HTTPСоединение.Записать на HTTPСоединение.ОтправитьДляОбработки

    Reply
  5. uno-c

    Отлично!

    Конечно же, важно не путать методы HTTPСоединение (HTTPConnection):

    Записать (Put)

    ОтправитьДляОбработки (Post)

    Получить (Get)

    Reply
  6. kasper076

    Реально работает. Спасибо.

    Reply
  7. kasper076

    В данном примере сертификат содержится в файле формата JSON. А если сертификат в файле cer, как тогда можно достать закрытый ключ?

    Reply
  8. uno-c

    (7)В .cer обычно только открытый ключ. Google выдавал еще .p12 — из него ключи можно достать например через powershell.

    $cert = New-Object system.security.cryptography.x509certificates.x509certificate2
    $pat=»d:UNSAFEdellmySert.p12″
    $password=»notasecret»
    $flags = «UserKeySet,Exportable»
    $cert.Import($pat, $password, $flags)
    $myXml = $cert.PrivateKey.ToXmlString($True)
    $myXml | Out-File «d:UNSAFEdellFromPower.xml»

    Ключи и параметры для ускоренного расчета будут видны в файле FromPower.xml, ими можно вычислять цифровую подпись для гугла.

    Reply
  9. kasper076

    (8) Некорректно сформулировал свой вопрос. Вот тут ты используешь COMОбъект(«System.Security.Cryptography.RSACryptoServiceProvider») для подписи файла по алгоритму RSA-sha256. А в обработке из этой темы СОМ-объект не используется. Возможно ли только средствами 1С подписать и поверить подпись RSA-sha256, если сертификат лежит в файле cer?

    Reply
  10. uno-c

    (9)Если закрытый ключ содержится в файле — думаю можно, т.к. у эски есть инструменты для работы с двоичными данными. Но повторю, в .cer как правило нет закрытого ключа, нужен .pfx или .p12.

    Reply
  11. kasper076

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

    Я попробовал разобрать файл cer используя алгоритм из обработки. Но результат получился не корректным. Получились классы не входящие в формат ASN.1

    Reply
  12. uno-c

    (11)Шифруется потому что содержит закрытый ключ. Значит, если сильно хочется эской — нужно разбирать и расшифровывать ). Посмотрел пару закрытых ключей в pfx — они зашифрованы 3DES SHA-1. Но это совсем для 1с-гурманов ). Менеджер криптографии вроде только с асимметричным шифрованием работает, т.е. придется алгоритм расшифровки вручную прописывать, если хочется без COM или Native.

    Reply
  13. kasper076

    (12) Ну мы не такие 🙂 . Решили прост отказаться от RSA-sha256. Нам нельзя использовать сторонние библиотеки.

    Reply
  14. uno-c

    (13)Да, средствами эски расшифровывать закрытый ключ из pfx неоправданно трудоемко будет. Разве что для какой-нибудь дипломной работы — из теоретического интереса решить задачку.

    Reply
  15. Cyberhawk

    В чем отличие между двумя вложениями?

    Reply
  16. uno-c

    (15)Здравствуйте! В версии (8.3.9) дописаны некоторые функции для работы с двоичными данными, которые отсутствуют в платформе 8.3.9. Если версию обработки 8.3.9 запустить на более поздних платформах — то будет ругаться из-за конфликта имен функций встроенных в платформу и написанных мной, тогда нужно просто удалить область

    #Область Для_8_3_9
    // в 8.3.10 эти функции встроены в платформу
    
    &НаСервере
    Функция ПолучитьHexСтрокуИзБуфераДвоичныхДанных(Буфер)
    …
    #КонецОбласти
    Reply
  17. Cyberhawk

    (16) Благодарю за ответ и за публикацию

    Reply
  18. skyboy13

    Про SHA256 в 1С, это соответственно АлгоритмХеширования. Что-то у него даже описания нет….

    Reply
  19. uno-c

    (18)Вы имеете в виду описание в СП МенеджерКриптографии-АлгоритмХеширования-SHA256? Выше в статье я писал «На данный момент встроенным Менеджером Криптографии 1ска не умеет подписывать SHA256 хеш, а гугл другого не приемлет.» В СП его нет, но можно обратиться к любому модулю криптографии Новый МенеджерКриптографии(<ИмяМодуляКриптографии …), потом .

    ПолучитьИнформациюМодуляКриптографии(), и в нем есть массив .АлгоритмыХеширования — там найдете полный перечень алгоритмов хеширования, с которыми справится 1С платформенными средствами, SHA256 пока там отсутствует.

    Reply
  20. ivanovkpd

    Добрый день.

    Я немного соптимизировал, как мне кажется, генерацию JSON для передачи на гугл

    Во первых, использую метод batchUpdate, он позволяет избавиться от необходимости указания range в url, во вторых ф-я глобального контекста ЗаписатьJSON, позволяет избавиться от вложенного цикла.

    Функция ДжонИзМассива(Знач Масс, Рэйнж, Знач НачСтрока = 1,
    Знач НачКолонка = 1, Знач Лист = «Лист1») Экспорт
    
    Данные = Новый Структура();
    ValueRange = Новый Структура();
    Колонок = Масс.количество();
    Строк = Масс[0].Количество();
    Рэйнж = СтрШаблон(«%1!%2:%3», Лист, НотацияА1(НачКолонка, НачСтрока), НотацияА1(НачКолонка
    + Колонок + 1, НачСтрока + Строк + 1));
    
    ValueRange.Вставить(«range», Рэйнж);
    ValueRange.Вставить(«majorDimension», «COLUMNS»);
    ValueRange.Вставить(«values», Масс);
    
    Данные.Вставить(«valueInputOption», «USER_ENTERED»);
    Данные.Вставить(«data», ValueRange);
    
    Джсон = Новый ЗаписьJSON();
    Джсон.УстановитьСтроку();
    ЗаписатьJSON(Джсон, Данные);
    Возврат Джсон.Закрыть();
    КонецФункции
    

    Показать

    Reply
  21. uno-c

    (20)Здравствуйте! Цель этой обработки — получить access token. Гугл-таблица в ней заполняется лишь для проверки работоспособности полученного токена. ЗаписатьJSON и ПрочитатьJSON глобального контекста — да, как правило удобней и лаконичней эти функции использовать.

    Reply
  22. ivanovkpd

    (21) Да я ж не спорю! Она мне ОЧЕНЬ помогла, хоть и промучился несколько дней из-за невнимательности своей 🙂

    Я не вам в упрек ни в коем случае.

    Reply
  23. ivanovkpd

    (21) Кстати обнаружил неприятный нюанс — Googe Sheet API не понимает JSON-format даты/времени.

    Посему, перед применением ЗаписатьJSON приходится все даты конвертить в строки, все в тех же мерзких циклах. 🙁

    Reply
  24. uno-c

    (22)Я к тому, что вдруг кто будет читать перед тем, как скачать эту обработку. Обработка позволит не тратить время на изучение нудной процедуры получения токена. Плюс она отвязана от операционной системы, работает на линуксе, т.к. не использует обращение к криптопровайдеру через COM для вычисления цифровой подписи, а вычисляет сама чистой арифметикой. Но дальнейшее использование полученного токена — это уже предмет других примеров-обработок.

    Reply
  25. uno-c

    (23)В спецификации JSON отсутствует дата, отсюда и проблема. Т.е. нету такого понятия, как «JSON-format даты/времени». RFC7159:

    JSON can represent four primitive types (strings, numbers, booleans, and null) and two structured types (objects and arrays).

    Использующие обмен данными в формате JSON сами принимают решение, как передавать даты в форме строки. В 1С есть три встроенных варианта для этого — например в функциях глоб.контекста ПрочитатьДатуJSON(…) ЗаписатьДатуJSON(…) — ISO, JavaScript, Microsoft. В глоб.контекст. ЗаписатьJSON — тоже третьим параметром можно передать настройки сериализаци, в том числе для даты с тремя этими вариантами. Но google sheets api, видимо, какой-то четвертый формат использует )

    Reply
  26. ivanovkpd

    (25) а вот настройки сериализации я что-то не попробовал, спасибо.

    Гугл принимает dd-MM-yyyy HH:mm:ss, если стоит параметр USER_ENTERED, а RAW я не пробовал 🙂

    (24) Не, обработка просто шикарная! 100500й раз спасибо вам 🙂 Вы, по-моему вообще единственный кто реализовал 2-leg авторизацию.

    Reply
  27. uno-c

    (26)

    а RAW я не пробовал 🙂

    Судя по инструкции гугла, можно и не пробовать ):

    RAW The values the user has entered will not be parsed and will be stored as-is.

    USER_ENTERED The values will be parsed as if the user typed them into the UI. Numbers will stay as numbers, but strings may be converted to numbers, dates, etc. following the same rules that are applied when entering text into a cell via the Google Sheets UI.

    В JSON либо строка либо число, других вариантов в данном случае нет. И в случае RAW — все останется как есть — строкой или числом.

    Reply
  28. uno-c

    (26)

    100500й раз спасибо вам 🙂

    А Вам спасибо за отзыв !

    Reply
  29. Cthulhu

    (20)

    упс. выглядит красиво (я так понял в вывод (начиная с А1 в целевой гугл-таблице) кидается содержимое Масс.

    тупой вопрос: а как собственно Масс формируется для таб.документа (или области)?..

    прим:
    Reply
  30. Cthulhu

    спасибо!

    тупые (снова, уж извините) вопросы.

    а как именно «взаимоувязываются» линк на гугл-таблицу с кодом? в смысле — где и как в коде указывается, что именно эта таблица правится через api?..

    Reply
  31. Cthulhu

    (32) о тут уже оказывается отрезан обмен сообщениями — тупо редирект в телегу и прочее (которого у меня нет).

    спасибо, добрый человек. надеюсь автор топикстартер извинит за диалог тут.

    1) понятно что в константе — непонятно что оно (ТабИД) собой представляет и как его получить…

    2) вижу у тебя тупо ТЗ выгоняется в гугль-шит? а как таб.документ?

    Reply
  32. uno-c

    (32)Нехило Вы этак, весь мой авторский код «криптопровайдера» спалили.

    Reply
  33. ivanovkpd

    (33)

    Мне табДокумент не нужен, я выгружаю в гугль результаты запроса.

    Я думаю ТабДок проще гнать сразу в массив, без ТЗ. И, скорее всего, просто циклами.

    Я еще почему с ТЗ заморочился и с массивом «по столбцам», у меня выгружается таблица > 50к строк. Гнать ее вложенными циклами, по моему мнению, менее эффективно, чем через «ВыгрузитьКолонку» хотя я и не тестировал. Но! даты приходится конвертировать в строку после выгрузки колонки. Глупый Гугд не принимает дату в ISO.

    Reply
  34. Cthulhu

    извините, снова тупые вопросы (пытаюсь пере-до0пилить немного под себя)

    1. Проект создан, в «Бибилиотека» подключены API googlesheets и googledrive

    2. Учетные параметры выглядят вот так (аттач).

    сначала запутался — который json и как выгружать… с горем пополам выгрузил тот, который обработка съела (на другой ругалась).

    3. от этой же учетки (в которой создан проект) — создал и сохранил гугль-таблицу. доступ — всем на всё (потом буду экспериментировать с ограничениями, дошло).

    4. из джсона — разобрать сертификат — получил для себя мыло (далее использую его).

    5. получить токен —

    4. для этой гугль-таблицы, с мылом из разобранного сертификата — сгенерировать токен.

    5. список листов — получаю запросом с этим токеном+мылом без проблем (с любым токеном, вообще-то — но это наверное из-за полного расшаривания).

    6. попытка загнать данные в эту таблицу (на первый лист, с #gid=0) с этими же (своим!) токеном и (своим!) мылом — error 403 «PERMISSION_DENIED».

    Reply
  35. Cthulhu

    (36)

    прим.: не на токен ругается…

    {

    «error»: {

    «code»: 403,

    «message»: «The caller does not have permission»,

    «status»: «PERMISSION_DENIED»

    }

    }

    — файл создан под тем аккаунтом, в который выполнен вход, и расшарен максимально, на весь инет (редактирование)

    а может где-то в консоли какой-нибудь можно посмотреть — кто лез через апи к файлу?

    Reply
  36. uno-c

    (37)Думаю, стОит попробовать шаг-в-шаг повторить действия, описанные в статье, т.к. они позволяют редактировать таблицу. А уж потом экспериментировать с доступами «всем на всё» и проч.

    Reply
  37. ivanovkpd

    (36) Я так думаю в настройках доступа к таблице у тебя не указан в явном виде имейл сервис-аккаунта

    Reply
  38. ivanovkpd

    (38) Ну таки да, я делал, у меня работает 🙂

    Да, еще момент, насколько я понял, учетка в которой живет таблица должна быть для частного лица, иначе все эти АПИ вроде бы платные.

    Reply
  39. Cthulhu

    (40)

    ааагрррхх… учетка, конечно же (мне ж не для домашней булгахтерии эта вся беда нужна…) — корпоративная…..

    Reply
  40. uno-c

    (41)

    корпоративная

    Cthulhu если есть возможность — отпишитесь пжл., действительно ли дело в корпоративной учетке было. Если на корпоративную таблицу дать доступ емейлу корпоративного сервис-аккаунта — то все равно «PERMISSION_DENIED»?

    Reply
  41. ivanovkpd

    (42) Да, у меня так. я тоже начал с бизнес-учетки.

    Потом по итогу просто сделал отдельную «частную».

    Reply
  42. Cthulhu

    (42)

    отписываюсь.

    *** Вошел в личный аккаунт (не корпоративный).

    1.1. Создал API-ключ сервисного аккаунта (сохранение json-файла на локал).

    1.2. Создал новый гугль-шит, максимально расшарил (доступен всем на все).

    2. Запустил обработку (подпиленную, адрес гугль-шита в текстовое поле ввода, e-mail в коде — из поля ввода).

    2.1. Выбрал json-файл из п.1.1.

    2.2. «Разобрать сертификат», «Получить токен» == «Подпись верна: Да»

    2.3. Вбил свой адрес гугль-шита (из п.1.2).

    2.4. «Отправить Столбец в гугл» == «Успешно» (и столбец в моем гугл-шите (см.п.1.2) изменился как заказано.

    *** Вошел в корпоративный аккаунт.

    пп.1.1-2.3, повторил в полном объеме с аналогичными результатами (все Ок).

    2.4. «Отправить Столбец в гугл» == { «error»: { «code»: 403, «message»: «The caller does not have permission», «status»: «PERMISSION_DENIED» } }

    Reply
  43. Cthulhu

    (39)

    я в коде заменил — вместо явного указания он тянется из соответствующего поля ввода (которое пере-вычисляется по «разобрать сертификат»).

    Reply
  44. ivanovkpd

    (45) Речь не про код 1С, а про доступ в гугле. У таблицы явно должен быть разрешен доступ томы «мылу», что зашито в JSON, при гнереации сервис-аккаунта

    Reply
  45. ivanovkpd

    (44) Да-да, все точно так. с корпоративной не работает. Денег наверное надо дать. ну или они там дают пробник вроде на год.

    Reply
  46. Cthulhu

    (46)

    т.е. ты имеешь ввиду, что по мылу, указанному в json-файле API-ключа сервисного аккаунта — нет доступа к API? по-моему это бред, извини…

    Reply
  47. uno-c

    (48)Имелось в виду, что возможно у API особый подход к разрешениям для сервисных аккаунтов — т.е. возможно «все для всех» на них не всегда распространяется, и например, сервисному емейлу обязательно нужно персональное разрешение на редактирование таблицы. Но, судя по Вашему весьма полезному эксперименту в (44) — проблема была исключительно в «корпоративности» учетки.

    Reply
  48. ivanovkpd

    (48) сервисной учетке разрешение на Sheets API дано?

    Бред или не бред, но у ТС и у меня работает. Значит, надо где-то у тебя искать. Вот мы и пытаемся нащупать проблему.

    Reply
  49. uno-c

    (50)Уже ведь выяснили, что проблема исключительно в корпоративной учетке, Cthulhu специально для этого эксперимент провел (44). Корпоративку ему отбивало «PERMISSION_DENIED», но когда он сделал из личной учетки — то «и столбец в моем гугл-шите (см.п.1.2) изменился как заказано.»

    Reply
  50. Cthulhu

    (50)

    в обеих учетках «api включен» для «google drive» и для «google sheets».

    Reply
  51. uno-c

    (11)Извините, просто памятка. На днях наткнулся, удобная штуковина для разбора ASN.1 https://lapo.it/asn1js/

    И уж раз начал писать — то в моей обработке — да, я не делал универсальный разбор любых файлов ASN, а ограничился только тем, что было необходимо для разбора json-сертификатов от Гугла.

    Reply
  52. Ruschel

    Кто-то пробовал использовать данный способ получения токена сервисной УЗ для управления корпоративной почтой(возможно в личной аналогично, не пробовал) через GMail API? Чтобы поменять настройки ящика другому человеку(автоответ и подпись) нужно включить domain-wide делегирование. Не совсем понятно, нужно ли при получении токена указывать адрес юзера в поле стурктуры «sub», которому необходимо изменить настройки? Попытка добавление этого поля в структуру запроса токена выдается ошибка ниже. При этом все scope для сервисного аккаунта выданы и без sub все корректно выдается. Но с таким выданным токеном не удается отредактировать чужие настройки.

    {

    «error»: «unauthorized_client»,

    «error_description»: «Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.»

    }

    Reply
  53. uno-c

    (54)

    https://www.googleapis.com/auth/gmail.settings.sharing Manage sensitive mail settings, including forwarding rules and aliases.

    Note:Operations guarded by this scope are restricted to administrative use only. They are only available to G Suite customers using a service account with domain-wide delegation. Restricted

    У Вас есть платный G Suite и domain-wide delegation ?

    Reply
  54. Ruschel

    (55)

    да, платный GSuite и включенное делегирование в проекте и сервисной УЗ.

    Как раз этот scope и API пробую использовать.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *