Битрикс24 коробка – ошибка OAuth авторизации, куда копать? — Хабр Q&A

Что такое grant?

Grant — это данные, которые представляют из себя успешную авторизацию клиента владельцем ресурса, используемые клиентом для получения access token.

Например, когда мы где-либо аутентифицируемся с помощью Google, перед глазами всплывает уведомление. В нём говорится, что такой-то сервис хочет получить доступ к данным о вас или к вашим ресурсам (выводятся запрашиваемые scope-token). Это уведомление называется «Consent Screen».

В момент, когда нажимаем «ОК», в базу данных попадает тот самый grant: записываются данные о том, что такой-то пользователь дал такие-то доступы такому-то сервису. Клиент получает какой-то идентификатор успешной аутентификации, например строку, которая ассоциируется с данными в базе данных.

Существует 4 1 способа получения grant — grant type:

Что такое оauth2.0?

Разработку нового Auth мы решили начать с изучения доступных протоколов и технологий. Самый распространённый стандарт авторизации — фреймворк авторизации OAuth2.0. 

Стандарт был принят в 2022 году, и за 8 лет протокол меняли и дополняли. RFC стало настолько много, что авторы оригинального протокола решили написать OAuth 2.1, который объединит все текущие изменения по OAuth 2.0 в одном документе. Пока он на стадии черновика.

Актуальная версия OAuth описанна в RFC 6749. Именно его мы и разберем. 

OAuth 2.0 — это фреймворк авторизации.

Он описывает, как должно реализовываться взаимодействие между сервисами для обеспечения безопасной авторизации. Многие нюансы описаны достаточно подробно, например, flow взаимодействия узлов между собой, но некоторые отдаются на откуп конкретной реализации.

Особенности:

Разберёмся подробнее в особенностях.

Authorization code

Самый распространённый flow на данный момент. В основном используется для confidential клиентов, но с появлением дополнительной проверки с помощью PKCE, может применяться и для public-клиентов. 

Resource owner password credentials flow


По текущим рекомендациям безопасности описанных в

, данный flow не рекомендуется использовать вовсе из-за явных проблем с безопасностью.

Абстрактный oauth 2.0. flow c применением access token

Мы рассмотрели роли, рассмотрели виды токенов, а также как выглядят scope. Посмотрим на flow предоставления доступа к сервису.

Ниже представлена абстрактная схема (или flow) взаимодействия между участниками. Все шаги на данной схеме выполняются строго сверху вниз. Разберём детальнее.

Клиент получает одобрение от resource owner, на основе которого ему выдаётся доступ к ресурсу. Всё просто. А будет ли так же просто, если мы добавим в эту схему работу с refresh token?

Абстрактный oauth 2.0. flow c применением refresh token

Первый и второй шаги опущены из данной схемы — они ничем не отличаются от схемы абстрактного flow выше.

Схема подробнее:

Аутентификация на внешних сервисах посредством oauth

 В данной  статье на примере подключения к сервисам Google я продемонстрирую реализацию подключения к сервисам Google при помощи аутентификации по протоколу OAuth 2.0 на языке программирования 1С.  В статье будет показана реализация подключения к сервису календарей Google с помощью авторизации по протоколу OAuth 2.0. Получение списка календарей и загрузка событий календаря на форму.

Сперва следует немного рассказать о популярном ныне протоколе аутентификации OAuth который сейчас используется повсеместно.

OAuth – открытый протокол (схема) авторизации, который позволяет предоставить третьей стороне ограниченный доступ к защищённым ресурсам пользователя без необходимости передавать ей (третьей стороне) логин и пароль(ru.wikipedia.org/wiki/OAuth). Суть такого вида аутентификации является отсутствие необходимости передавать логин и пароль к персональным данным или регистрироваться на сервисе проходя нудную процедуру регистрации затем хранить пароли от разных аккаунтов, да и сервисам полегче не нужно создавать системы хранения учётных данных и ещё и отвечать за их возможную утечку. Пользователь может быть уверен что приложению будет доступен только тот набор данных которые он разрешил.  Также можно гарантировать что приложение будет продолжать работать пока пользователь не запретит ему доступ к своим данным, при этом пользователь может менять пароль и это никак не скажется на работе приложения. После получения доступа приложение может работать не требуя от пользователя подтверждения своих привилегий доступа (хотя у сервисов предоставляющих доступ есть свои ньюансы на этот счёт).

Итак как работает этот протокол аутентификации. В стандарте описана несколько схем работы протокола на все случаи жизни от авторизации на Web серверах до аутентификации на Smart-телевизорах. Жаждущие подробносей могут углубиться в чтение на https://www.digitalocean.com/community/tutorials/oauth-2-ru. В статье я не буду подробно останавливаться на деталях и особенностях этого протокола. В статье будет описан только наиболее часто используемый способ авторизации – получение токена доступа через код авторизации.

Итак аутентификация состоит из трёх последовательных этапов и одного предварительного связанного с регистрацией 1С обработки в качестве приложения на сервисах Google. Для приложения мы активируем API для тех типов данных, доступ к которым нам понадобится для приложения, ещё в приложении мы создадим OAuth идентификатор нашего приложения.

Начнем с предварительного этапа, регистрации приложения.

Нам потребуетя войти в консоль Google Api с действующей учетной записи Google по ссылке https://console.developers.google.com/apis/dashboard.

Создадим проект.

Следующим шагом нам нужно определиться какие API Google мы будем использовать, их надо активировать. Сделать это можно перейдя кликнув на ссылке перейти к обзору API ниже на рисунке. Для наших целей достаточно Calendar API.

Осталось создать идентификатор учётных данных OAuth. На картинке жмем Учётные данные,
Теперь создаём идентификатор OAuth

Далее открывается вот такое модальное окно, в котором Google предлагает нам настроить окно подтверждения пользователем доступа к их данным. Это то самое окно в котором вы даёте согласие на доступ к своим учётным данным. Настроим, жмём на Set up Consent Screen.

Здесь нас интересуют области действия для API Google. Это те разрешения на доступ к персональным данным которые мы будем просить у пользователя. Для примера достаточно прав чтения данных календаря (read only).  В нашем примере нам нужны такие права

Нажимаем сохранить. И переходим в учетные данные для создания идентификатора OAuth.

Итак, с консолью Google мы закончили. Для удобства можно скачать файл JSON. В нём содержатся все требуемые данные которые потребуются нам в дальнейшем.
Пора заняться подключением из 1С.
И это только предварительный этап😊.

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

Версия платформы на которой тестировалась обработка 8.3.14, в которой, наконец-то, был заменен веб-движок с престарелой версией Internet Explorer на современный WebKit. Что само по себе открывает огромные возможности по взаимодействию с интернетом из 1С.

Первый этап.  1.    Обращение к авторизационному серверу за разрешением пользовательских данных. Авторизационный сервер это сервис который хранит пользовательские данные и предоставляет доступ к пользовательским данным. На этом этапе формируется GET запрос со следующими параметрами

client_id – идентификатор нашего приложения

redirect_uri – веб-страница на которую вы будете переадресованы после того как пользователь разрешил доступ к своим данным

scope  – это области пользовательских данных, на которые мы будем просить разрешения у пользователя. Помните мы настраивали их в окне аутентификации. Задаются в качестве текстовой строки через пробел.

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

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

Кнопка Авторизация  содержит код начала процесса авторизации

&НаКлиенте
Процедура Авторизоваться(ОписаниеДействия = Неопределено)
	
	ПараметрыФормы = новый структура("Адрес", АдресСтраницыАутентификации());
	ОО = Новый ОписаниеОповещения("ОбработатьAccessToken", ЭтаФорма, ОписаниеДействия);		
	ОткрытьФорму("ВнешняяОбработка.АутентификацияGoogle.Форма.ФормаАутентификации", ПараметрыФормы, Элементы.Авторизоваться, ,,,ОО, РежимОткрытияОкнаФормы.БлокироватьОкноВладельца);
	
КонецПроцедуры

 Мы просто открываем вспомогательную форму авторизацию с одним параметром – адресом страницы аутентификации, который формирует для нас функция:

&НаКлиентеНаСервереБезКонтекста
Функция АдресСтраницыАутентификации()

	ПараметрыURL = Новый Структура;
	Адрес = "https://accounts.google.com/o/oauth2/v2/auth";
	ПараметрыURL.Вставить("client_id", "<ваш уникальный идентификатор приложения из Google API console>.apps.googleusercontent.com");
	ПараметрыURL.Вставить("redirect_uri", "http://localhost");
	ПараметрыURL.Вставить("scope", "https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events.readonly  https://www.googleapis.com/auth/calendar");
	ПараметрыURL.Вставить("response_type", "code");
	ПараметрыURL.Вставить("prompt", "consent"); //Пользователю отображается только окно разрешения доступа к его пользовательским данным
	Возврат Адрес(Адрес, ПараметрыURL);

КонецФункции // ПолучитьAuthToken()

В результате работы кода кнопки открывается окно аутентификации. После того как пользователь нажал кнопку Allow(разрешить), сервер Google перенаправит нас на страницу указанную при регистрации приложения – http://localhost, которая попытается открыться в том же поле HTML Документа, но скорее всего не сможет, если у вас не запущен локальный веб-сервер. В обработчике события поля HTML документа мы извлечём полученный код аутентификации.

Битрикс24 коробка - ошибка OAuth авторизации, куда копать? — Хабр Q&A

Ниже показан код формы, в котором нам нужно извлечь аутентификационный код из параметров командной строки, так как 1С не предоставляет возможности в случае localhost обработать параметры адресной строки(напишите в комментариях, если существует способ), но отображает адрес страницы редиректа в теле полеHTML документа, то код извлекаем из тела страницы.


&НаКлиенте
Процедура ПолеHTMLДокументСформирован(Элемент)
	
	Адрес = Элемент.Документ.URL;
	if Адрес = "about:blank" Then
		InnerHTML = Элемент.Документ.body.InnerHTML;
		AuthCode = AuthCode(InnerHTML);
		Закрыть(AuthCode);
	endif

КонецПроцедуры

&НаКлиентеНаСервереБезКонтекста
Функция AuthCode(URL)
	НачалоКода = СтрНайти(ВРЕГ(URL), ВРЕГ("code="), НаправлениеПоиска.СНачала);
	Если НачалоКода = 0 Тогда
		Возврат "";
	КонецЕсли;
	НачалоКода = НачалоКода   5;
	КонецКода = СтрНайти(ВРЕГ(URL), ВРЕГ("&amp;"), НаправлениеПоиска.СНачала, НачалоКода); 
	Возврат Сред(URL, НачалоКода, КонецКода - НачалоКода);
КонецФункции

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	Адрес = Параметры.Адрес;
КонецПроцедуры




На основную форму возвращаем код аутентификации который позволит нам обменять его на токен доступа на следующем этапе еще одним запросом к серверу.

Второй этап. Полученный код следует обменять на токен доступа(Access token). Токен доступа это идентификатор который выдаётся авторизационным сервером приложению на определенный промежуток времени для доступа к данным пользователя. Запрос выполняется методом POST со следующими параметрами.

code – полученный авторизационный код

client_id – идентификатор приложения из Console APi Goolge

client_secret – секретный код приложения из Console APi Google

redirect_uri – адрес переадресации указанный в Console APi Google

grant_type – содержит значение authorization_code

&НаКлиенте
Процедура ОбработатьAccessToken(AuthCode, ОписаниеДействия) Экспорт

	Если AuthCode <> Неопределено И Не ПустаяСтрока(AuthCode) Тогда
		Tokens = Tokens(AuthCode);
		AccessToken = AccessToken(Tokens);
		RefreshToken = ?(RefreshToken(Tokens) = Неопределено, RefreshToken, RefreshToken(Tokens));
		Календари = ЗаполнитьКалендари(AccessToken);
		ЗаполнитьКалендариНаФорме(Календари);
		ПоказатьОповещениеПользователя("Авторизация успешна",,,,СтатусОповещенияПользователя.Информация);
		Если ОписаниеДействия <> Неопределено Тогда
			СтрокаВыполнить = ОписаниеДействия.Действие   "("   AccessToken   ","   ОписаниеДействия.ПараметрыДействия   ")";
			Выполнить(СтрокаВыполнить);
		КонецЕсли;
	Иначе
		AccessToken = "";
	КонецЕсли;

КонецПроцедуры // ОбработатьAccessToken()

&НаКлиентеНаСервереБезКонтекста
Функция AccessToken(Tokens)
	
	Если типЗнч(Tokens ) = Тип("Структура") Тогда
		Возврат Tokens.access_token;
	Иначе
		Возврат Неопределено;
	КонецЕсли;
	
КонецФункции // AccessToken()

&НаКлиентеНаСервереБезКонтекста
Функция RefreshToken(Tokens)

	Если Tokens.Свойство("refresh_token") Тогда
		Возврат Tokens.refresh_token;
	Иначе
		Возврат Неопределено;
	КонецЕсли;

КонецФункции // RefreshToken()

&НаСервереБезКонтекста
Функция Tokens(AuthCode)
	
	ПараметрыURL = Новый Структура;
	АдресЗапроса = "https://www.googleapis.com/oauth2/v4/token";
	ПараметрыURL.Вставить("client_id", "<ваш идентификатор приложения из Google API Console>.apps.googleusercontent.com");
	ПараметрыURL.Вставить("redirect_uri", "http://localhost");
	ПараметрыURL.Вставить("code", AuthCode);
	ПараметрыURL.Вставить("client_secret", "<ваш secret key>");
	ПараметрыURL.Вставить("grant_type", "authorization_code");
	
	АдресЗапроса = Адрес(АдресЗапроса, ПараметрыURL);
	СтруктураURI = СтруктураURI(АдресЗапроса);
	HTTPСоединение = новый HTTPСоединение(СтруктураURI.Хост,443 , , ,, 15, Новый ЗащищенноеСоединениеOpenSSL);
	headers = Новый Соответствие;
	headers.Вставить("User-Agent", "Mozilla");
	headers.Вставить("Host", СтруктураURI.Хост);
	headers.Вставить("Content-Type", "application/x-www-form-urlencoded");
	HTTPЗапрос = Новый HTTPЗапрос(СтруктураURI.ПутьНаСервере, headers);
	HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
	Если HTTPОтвет.КодСостояния = 200 Тогда
		Ответ = HTTPОтвет.ПолучитьТелоКакСтроку();
		ЧтениеJSON = новый ЧтениеJSON();
		ЧтениеJSON.УстановитьСтроку(Ответ);
		 Token = ПрочитатьJSON(ЧтениеJSON, Ложь);
		 Возврат Token;
	 Иначе
		 ВызватьИсключение "Произошла ошибка обращения к серверу,"   "Токен не получен"  
		 Символы.ПС   "Статус ответа сервера: "   HTTPОтвет.КодСостояния;
	КонецЕсли;
	
КонецФункции
&НаСервереБезКонтекста
Функция Адрес(Знач URL, Знач ПараметрыURL)
	
	Перем МассивПараметров;
	МассивПараметров = Новый Массив;
	Для каждого Параметр Из ПараметрыURL Цикл
		МассивПараметров.Добавить(Параметр.Ключ   "="   Параметр.Значение);
	КонецЦикла;
	URL = СокрП(URL);
	URL = ?(СтрЗаканчиваетсяНа(URL, "/"), URL, URL   "/"); 
	Возврат URL   "?"   КодироватьСтроку(СтрСоединить(МассивПараметров, "&"),	СпособКодированияСтроки.URLВКодировкеURL);

КонецФункции

В коде отправляем подготовленный POST-запрос и в случае успешного результата, получаем JSON ответ с токеном доступа(access token) и токеном обновления, который мы можем использовать при повторном запросе токена доступа когда действующий токен доступа истечёт.

На этом авторизация завершена, пришло время воспользоваться пройденной авторизацией и получить что-нибудь полезное.

Третий этап.

Имея токен доступа в получим список календарей и события выбранного календаря.

&НаКлиенте
Процедура ЗаполнитьКалендариНаФорме(Календари)

	Элементы.КалендариGoogle.СписокВыбора.Очистить();
	Если Календари.items.Количество() > 0 Тогда
		Для Каждого Календарь Из Календари.items Цикл	
			Элемент = Элементы.КалендариGoogle.СписокВыбора.Добавить(Календарь.id, Календарь.summary);	
		КонецЦикла;
		КалендариGoogle = Элементы.КалендариGoogle.СписокВыбора.Получить(0).Значение;
		КалендариGoogleПриИзменении(Неопределено);
	КонецЕсли;
	
КонецПроцедуры 

Функция ЗаполнитьКалендари(AccessToken)

	Возврат ПолучитьСписокКалендарей(AccessToken);
	
КонецФункции

Функция ПолучитьСписокКалендарей(AccessToken)
	
	ПараметрыURL = Новый Структура;
	АдресЗапроса = "https://www.googleapis.com/calendar/v3/users/me/calendarList";
	
	СтруктураURI = СтруктураURI(АдресЗапроса);
	HTTPСоединение = новый HTTPСоединение(СтруктураURI.Хост,443 , , ,, 15, Новый ЗащищенноеСоединениеOpenSSL);
	headers = Новый Соответствие;
	headers.Вставить("User-Agent", "Mozilla");
	headers.Вставить("Host", "www.googleapis.com");
	headers.Вставить("Content-Type", "application/x-www-form-urlencoded");
	headers.Вставить("Authorization", "Bearer "   AccessToken);
	headers.Вставить("Accept", "application/json");
	HTTPЗапрос = Новый HTTPЗапрос(СтруктураURI.ПутьНаСервере, headers);
	HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос);
	Если HTTPОтвет.КодСостояния = 200 Тогда
		Ответ = HTTPОтвет.ПолучитьТелоКакСтроку();
		ЧтениеJSON = новый ЧтениеJSON();
		ЧтениеJSON.УстановитьСтроку(Ответ);
		Календари = ПрочитатьJSON(ЧтениеJSON, ЛОжь);
		 Возврат Календари;
	 Иначе
		 ВызватьИсключение "Произошла ошибка обращения к серверу,"   "Токен не получен"  
		 Символы.ПС   "Статус ответа сервера: "   HTTPОтвет.КодСостояния;
	КонецЕсли;
	
КонецФункции

&НаКлиенте
Функция ПолучитьСписокСобытий(Знач AccessToken,Знач ИдКалендаря)
	
	ПараметрыURL = Новый Структура;
	АдресЗапроса = "https://www.googleapis.com/calendar/v3/calendars/{ИдКалендаря}/events";
	ИдКалендаря = КодироватьURI(ИдКалендаря);
	АдресЗапроса = СтрЗаменить(АдресЗапроса, "{ИдКалендаря}", ИдКалендаря); 
	ПараметрыURL = Новый Структура;
	ПараметрыURL.Вставить("key", "<Секретный ключ клиента>");
	
	АдресЗапроса = Адрес(АдресЗапроса, ПараметрыURL);
	
	СтруктураURI = СтруктураURI(АдресЗапроса);
	HTTPСоединение = новый HTTPСоединение(СтруктураURI.Хост,443 , , ,, 15, Новый ЗащищенноеСоединениеOpenSSL);
	headers = Новый Соответствие;
	headers.Вставить("Host", СтруктураURI.Хост);
	headers.Вставить("Content-Type", "application/x-www-form-urlencoded");
	headers.Вставить("Authorization", "Bearer "   AccessToken);
	headers.Вставить("Content-length", "0");
	headers.Вставить("Accept", "application/json");
	HTTPЗапрос = Новый HTTPЗапрос(СтруктураURI.ПутьНаСервере, headers);
	HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос);
	Если HTTPОтвет.КодСостояния = 200 Тогда
		Ответ = HTTPОтвет.ПолучитьТелоКакСтроку();
		ЧтениеJSON = новый ЧтениеJSON();
		ЧтениеJSON.УстановитьСтроку(Ответ);
		 СобытияКалендаря = ПрочитатьJSON(ЧтениеJSON, Истина);
		 Возврат СобытияКалендаря;
	 ИначеЕсли HTTPОтвет.КодСостояния = 401 Тогда
		 ЧтениеJSON = Новый ЧтениеJSON;
		 ЧтениеJSON.УстановитьСтроку(HTTPОтвет.ПолучитьТелоКакСтроку());
		 ОтветJSON = ПрочитатьJSON(ЧтениеJSON, ЛОЖЬ);
		 Если ОтветJSON.error.message = "Invalid Credentials" Тогда
			 //Обновить Token
			 AccessToken = AccessToken(ОбновитьТокен(RefreshToken));
			 Если ЗначениеЗаполнено(AccessToken) Тогда
				 Возврат ПолучитьСписокСобытий(AccessToken, ИдКалендаря);
			 Иначе
				 ПоказатьОповещениеПользователя("Необходима авторизация",,,,СтатусОповещенияПользователя.Информация);
				 ПараметрыДействия = Новый Массив;
				 ПараметрыДействия.Добавить(ИдКалендаря);
				 Авторизоваться(ОписаниеДействия("ПолучитьСписокСобытий", ПараметрыДействия));
			 КонецЕсли;
		 КонецЕсли;

	КонецЕсли;
	

КонецФункции

&НаКлиентеНаСервереБезКонтекста
Функция ОбновитьТокен(RefreshToken)

	Если Не ПустаяСтрока(RefreshToken) Тогда
		Возврат RefreshTokens(RefreshToken);
	Иначе
		Возврат Неопределено;
	КонецЕсли;

КонецФункции

&НаСервереБезКонтекста
Функция RefreshTokens(RefreshToken)
	
	ПараметрыURL = Новый Структура;
	АдресЗапроса = "https://www.googleapis.com/oauth2/v4/token";
	ПараметрыURL.Вставить("client_id", "<идентификатор приложения>.apps.googleusercontent.com");
	ПараметрыURL.Вставить("redirect_uri", "http://localhost");
	ПараметрыURL.Вставить("refresh_token", RefreshToken);
	ПараметрыURL.Вставить("client_secret", "<секретный ключ>");
	ПараметрыURL.Вставить("grant_type", "refresh_token");
	
	АдресЗапроса = Адрес(АдресЗапроса, ПараметрыURL);
	СтруктураURI = СтруктураURI(АдресЗапроса);
	HTTPСоединение = новый HTTPСоединение(СтруктураURI.Хост,443 , , ,, 15, Новый ЗащищенноеСоединениеOpenSSL);
	headers = Новый Соответствие;
	headers.Вставить("User-Agent", "Mozilla");//google-oauth-playground
	headers.Вставить("Host", СтруктураURI.Хост);
	headers.Вставить("Content-Type", "application/x-www-form-urlencoded");
	HTTPЗапрос = Новый HTTPЗапрос(СтруктураURI.ПутьНаСервере, headers);
	HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
	Если HTTPОтвет.КодСостояния = 200 Тогда
		Ответ = HTTPОтвет.ПолучитьТелоКакСтроку();
		ЧтениеJSON = новый ЧтениеJSON();
		ЧтениеJSON.УстановитьСтроку(Ответ);
		 Token = ПрочитатьJSON(ЧтениеJSON, Ложь);
		 Возврат Token;
	 Иначе
		 ВызватьИсключение "Произошла ошибка обращения к серверу,"   "Токен не получен"  
		 Символы.ПС   "Статус ответа сервера: "   HTTPОтвет.КодСостояния;
	КонецЕсли;
	
КонецФункции

&НаКлиенте
Процедура КалендариGoogleПриИзменении(Элемент)
	
	ИдКалендаря = Элементы.КалендариGoogle.СписокВыбора.НайтиПоЗначению(КалендариGoogle);
	СписокСобытий = ПолучитьСписокСобытий(AccessToken, ИдКалендаря.Значение) ;
	События.Очистить();
	Если События = Неопределено Тогда 
		Возврат;
	Конецесли;
	Для каждого Событие Из СписокСобытий["items"] Цикл
		
		Если Событие["start"] ["date"] = Неопределено Тогда
			ДатаСобытия = '00010101';
		Иначе
			ДатаСобытия = ПрочитатьДатуJSON(Событие["start"] ["date"], ФорматДатыJSON.ISO);
		КонецЕсли;
		Если Событие["summary"] = Неопределено Тогда
			ОписаниеСобытия = "";
		Иначе
			ОписаниеСобытия =  Событие["summary"];
		КонецЕсли;
		События.Добавить(ДатаСобытия, ОписаниеСобытия   "("    Формат(ДатаСобытия, "ДФ=dd.MM.yy")   ")");
	
	КонецЦикла;
КонецПроцедуры

В коде мы формируем пару GET запросов на получение календарей и событий календаря, адреса на которые отправляются запросы определены в документации Google, применительно к API календаря подробнее ознакомиться можно по ссылке здесь(англ.). Обратите особое внимание на заголовок Bearer в запросах в нем мы передаём наш токен доступа. Обязателен также задать заголовок Content-type как в примере. Остальные параметры упоминались ранее.

Стоит прокомментировать этот участок кода, здесь анализируем код ответа. Если в результате запроса получили ошибку “Invalid Credentials”, это означает что наш текущий токен доступа истёк, и тогда есть два варианта, или получить новый токен доступа повторно с помощью токена обновления (Refresh token) или запросить у пользователя повторно доступ к его данным через окно авторизации.

Если HTTPОтвет.КодСостояния = 200 Тогда
		Ответ = HTTPОтвет.ПолучитьТелоКакСтроку();
		ЧтениеJSON = новый ЧтениеJSON();
		ЧтениеJSON.УстановитьСтроку(Ответ);
		 СобытияКалендаря = ПрочитатьJSON(ЧтениеJSON, Истина);
		 Возврат СобытияКалендаря;
	 ИначеЕсли HTTPОтвет.КодСостояния = 401 Тогда
		 ЧтениеJSON = Новый ЧтениеJSON;
		 ЧтениеJSON.УстановитьСтроку(HTTPОтвет.ПолучитьТелоКакСтроку());
		 ОтветJSON = ПрочитатьJSON(ЧтениеJSON, ЛОЖЬ);
		 Если ОтветJSON.error.message = "Invalid Credentials" Тогда
			 //Обновить Token
			 AccessToken = AccessToken(ОбновитьТокен(RefreshToken));
			 Если ЗначениеЗаполнено(AccessToken) Тогда
				 Возврат ПолучитьСписокСобытий(AccessToken, ИдКалендаря);
			 Иначе
				 ПоказатьОповещениеПользователя("Необходима авторизация",,,,СтатусОповещенияПользователя.Информация);
				 ПараметрыДействия = Новый Массив;
				 ПараметрыДействия.Добавить(ИдКалендаря);
				 Авторизоваться(ОписаниеДействия("ПолучитьСписокСобытий", ПараметрыДействия));
			 КонецЕсли;
		 КонецЕсли;
		 //ВызватьИсключение "Произошла ошибка обращения к серверу,"   "Токен не получен"  
		 //Символы.ПС   "Статус ответа сервера: "   HTTPОтвет.КодСостояния;
		 Сообщить(HTTPОтвет.ПолучитьТелоКакСтроку());
		 Сообщить(AccessToken);
	КонецЕсли;





R03;

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

К статье прикреплена обработка содержащая полный код описанной в статье обработки. Из обработки удалены зарегистированные мной идентификаторы приложения, перед запуском вам нужно будет зарегистрировать собственное приложение в Google API.

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

Битрикс24 коробка – ошибка oauth авторизации, куда копать?

В общем удалось дебагером докопаться для проблемы, возможно кому то пригодится.

Пару фактов. Битрикс имеет модульную структуру. В битриксе есть модуль REST Api.
При установке модуля можно зарегистрировать обработчики событий. При установке, в данном случае REST Api, регистрирует обработчики для разных вариантов авторизации

if(!BitrixMainModuleManager::isModuleInstalled("oauth"))
		{
			$eventManager->registerEventHandler("rest", "onRestCheckAuth", "rest", "\Bitrix\Rest\OAuth\Auth", "onRestCheckAuth");
		}

		$eventManager->registerEventHandler("rest", "onRestCheckAuth", "rest", "\Bitrix\Rest\APAuth\Auth", "onRestCheckAuth");
		$eventManager->registerEventHandler("rest", "onRestCheckAuth", "rest", "\Bitrix\Rest\SessionAuth\Auth", "onRestCheckAuth");

которые потом при обращении к api и проверке перебираются в цикле и авторизуют в зависимости от параметров запроса.

foreach(GetModuleEvents('rest', 'OnRestCheckAuth', true) as $eventHandler)
		{
			$eventResult = ExecuteModuleEventEx($eventHandler, array($query, $scope, &$res));
			if($eventResult !== null)
			{
				return $eventResult;
			}
		}

Обращаем внимание что для регистрации обработчика авторизации через oAuth идет проверка на наличие установленного модуля

if(!BitrixMainModuleManager::isModuleInstalled("oauth"))
    {
     $eventManager->registerEventHandler("rest", "onRestCheckAuth", "rest", "\Bitrix\Rest\OAuth\Auth", "onRestCheckAuth");
   }

Так вот получается что на момент установки модуля Rest его не было потому событие не зарегистрировало.

По хорошему помогла бы переустановка модуля Rest, но в битрикс24 коробка у него жесткие зависимости и этого сделать нельзя не удалив пол портала.
60003859766e3137963019.png

Пришлось руками зарегистрировать обработчик и все заработало.

Вывод – код битрикс как всегда на высоте, в деле создания проблем на ровном месте.

Задача auth


Проблема авторизации в десятках сервисов встречалась ещё несколько лет назад — в начале «

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

У сервиса Auth есть три основные задачи:

Права доступа


Права доступа выдаются клиенту в виде scope. Scope – это параметр, который состоит из разделённых пробелами строк — scope-token.

Каждый из scope-token представляет определённые права, выдающиеся клиенту. Например, scope-token doc_read может предоставлять доступ на чтение к какому-то документу на resource server, а employee — доступ к функционалу приложения только для работников фирмы. Итоговый scope может выглядеть так: email doc_read employee.

В OAuth 2.0 мы сами создаём scope-token, настраивая их под свои нужды. Имена scope-token ограничиваются только фантазией и двумя символами таблицы ASCII — ” и .

На этапе регистрации клиента, в настройках сервиса авторизации клиенту выдаётся стандартный scope по умолчанию. Но клиент может запросить у сервера авторизации scope, отличный от стандартного. В зависимости от политик на сервере авторизации и выбора владельца ресурса, итоговый набор scope может выглядеть совсем иначе.

Проблемы


Первая версия Auth — часть монолита. Он использует свой собственный протокол общения с сервисами. Такая «схема» была необходима в тот момент, но за несколько лет работы проявились проблемы.

Auth — часть монолита. Следовательно, сервис привязан к релизному циклу, что лишает возможности независимой разработки и деплоя. Кроме того, придется разворачивать весь монолит, если захотелось развернуть Auth, например, при масштабировании сервиса.

Dodo IS зависит от Auth. В старой реализации внешние сервисы обращаются к Auth при каждом действии пользователя, чтобы валидировать данные о нём. Настолько сильная привязка может привести к остановке работы всей Dodo IS, если Auth «приляжет» по какой-то причине.

Auth зависит от Redis. Притом достаточно сильно — неисправность работы Redis’а приведёт к падению Auth’а. Мы используем Azure Redis, для которого заявленный SLA 99,9%. Это значит, что сервис может быть недоступен до 44 минут в месяц. Такие простои не позволительны.

Текущая реализация Auth использует свой протокол аутентификации, не опираясь на стандарты. В большинстве своих сервисов мы используем C# (если говорим о backend) и у нас нет проблем с поддержкой библиотеки для нашего протокола. Но если вдруг появятся сервисы на Python, Go или Rust, разработка и поддержка библиотек под эти языки потребует дополнительных затрат времени и принесет дополнительные сложности.

Текущий Auth использует схему Roles Based Access Control, которая базируется на ролях. Обычно с ролью выдаётся полный доступ к определённому сервису, вместо привязки к конкретному функционалу. Например, в пиццериях есть заместители управляющего, которые могут вести определенные проекты: составлять графики или учитывать сырьё.

Проблемы подтолкнули к тому, чтобы спроектировать и написать новую версию Auth. На старте проекта мы потратили 3 недели только на изучение стандартов авторизации и аутентификации OAuth 2.0 и OpenID Connect 1.0. 

Примечание. Утрированно, статья — это пересказ RFC, который приходилось перечитывать несколько раз, чтобы понять, что происходит вокруг. Здесь я постарался уйти от этой сложности и рассказать всё максимально просто, структурировано, кратко и без описания сложных вещей, например, какие символы может содержать в себе ответ сервиса.

Регистрация клиента

Способ регистрации клиента, например, ручной или service discovery, вы выбираете сами, в зависимости от

фантазии

конкретной реализации. Но при любом способе при регистрации, кроме ID клиента, должны быть обязательно указаны 2 параметра: redirection URI и client type.

Redirection URI — адрес, на который отправится владелец ресурса после успешной авторизации. Кроме авторизации, адрес используется для подтверждения, что сервис, который обратился за авторизацией, тот, за кого себя выдаёт.

Client type — тип клиента, от которого зависит способ взаимодействия с ним. Тип клиента определяется его возможностью безопасно хранить свои учётные данные для авторизации — токен. Поэтому существует всего 2 типа клиентов:

Токены

Токен в OAuth 2.0 — это строка, непрозрачная для клиента. Обычно строка выглядит как случайно сгенерированная — её формат не имеет значения для клиента. Токен — это ключ доступа к чему-либо, например, к защищённому ресурсу (access token) или к новому токену (refresh Token).

У каждого токена своё время жизни. Но у refresh token оно должно быть больше, т.к. он используется для получения access token. Например, если срок жизни access token около часа, то refresh token можно оставить жить на целую неделю. 

Refresh token опционален и доступен только для confedential клиентов. Пользуясь опциональностью токена, в некоторых реализациях время жизни access token сделано очень большим, а refresh token вообще не используется, чтобы не заморачиваться с обновлением.

За access token закреплён определённый набор прав доступа, который выдаётся клиенту во время авторизации. Давайте разберёмся, как выглядят права доступа в OAuth 2.0.

Вместо вывода

В этой статье я опустил много подробностей, чтобы максимально просто и доступно рассказать о самом важном. Например, типы запросов, как и в каком виде передавать параметры, какие символы допустимы в качестве значений для того. 

Если хотите погрузиться в тематику детальнее, то рекомендую в RFC 6749 (для OAuth 2.0) и RFC 8628 (для Device Flow). Кроме того, следить за актуальными версиями RFC можно на ресурсе, посвящённому OAuth.

Если статья была полезна и захотите подробностей — пишите в комментариях, и в следующих статьях расскажу о PKCE, о протоколе аутентификации OpenID Connect 1.0, о нашей реализации сервера аутентификации и многом другом.

Полезные ссылки:

Похожее:  ✔️ — отзывы о сайте, обсуждение и комментарии, похожие сайты, рейтинг, стоимость, доход и количество посетителей сайта, mylms официальный сайт и форум, вход в личный кабинет, анализ сайта онлайн на RANKW.RU

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

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