Вводные данные:
Прежде чем переходить к основной теме статьи, необходимо сделать лирическое отступление и описать некоторые неизвестные слова для неискушенного разработчика.
OAuth 1.0a — это открытый протокол авторизации, который позволяет предоставить третьей стороне ограниченный доступ к защищённым ресурсам пользователя без необходимости передавать ей (третьей стороне) логин и пароль. С детальным описанием протокола можно ознакомится в информационном документе RFC 5849.
OpenID 2.0 — открытый стандарт децентрализованной системы аутентификации, предоставляющей пользователю возможность создать единую учётную запись для аутентификации на множестве не связанных друг с другом интернет-ресурсов, используя услуги третьих лиц.
QuickBooks — представляет собой пакет программного обеспечения для бухгалтерского учета. Продукты QuickBooks ориентированы главным образом на малые и средние предприятия и предлагают приложения для бухгалтерского учета на местах, а также версии на основе облачных вычислений.
Проблематика:
Многие компании за границей используют QuickBooks для бухгалтерского учета и все конфигурации «1С:Предприятие 8», которые фирма 1С предоставляет через известный сайт для западного рынка, пока не могут закрыть вопросы бухгалтерского учета. В то же время конфигурации подходят для складского учета и для других целей. На этом стыке возникает потребность в интеграции систем. Большинство программистов привыкли, что интеграция — это обмен сообщениями определенного формата, но когда они встречают столь сложную авторизацию и аутентификацию невольно опускаются руки.
QuickBooks использует комбинацию OAuth 1.0a + OpenID 2.0 для интеграции через приложение (App). Приложение можно создать по ссылке. После создания приложения станут доступны ключи доступа. Вот об комбинировании этой солянки и пойдет речь ниже.
ШАГ 1. Создаем приложение:
После создания приложения, необходимо получить ключи разработчика для песочницы. В нашем случае они будут:
oauth_consumer_key = qyprdEQRuexcfM4P05zd2vB0baw2TB
oauth_consumer_secret = AbQpDjBAQlTHt08GBT9yktfuklrZJkvBL7Hswhoc
ШАГ 2. Получаем request token
Приложение должно запросить набор временных учетных данных token, также известный как request token. Он еще не связаны с какой-либо компанией QuickBooks конкретного пользователя. Приложением будет выступать конфигурация «1С:Предприятие 8» и для прохождения данного этапа необходимо реализовать некоторую базовую функциональность:
// Returns unique token your application should generate for each unique request. // // Returns: // String. // Function OAuthNonce() Return String(New UUID); EndFunction // OAuthNonce() // Returns default oauth signature method name. // // Returns: // String. // Function OAuthSignatureMethod() Return "HMAC-SHA1"; EndFunction // OAuthSignatureMethod() // Returns a sequence of characters or encoded information identifying when a certain // event occurred, usually giving date and time of day, sometimes accurate to a small // fraction of a second. // // Returns: // String. // Function OAuthTimestamp() Return Format(CurrentSessionDate() - Date("19700101"), "NG=0"); EndFunction // OAuthTimestamp() // Returns the OAuth version 1.0. // // Returns: // String. // Function OAuthVersion() Return "1.0"; EndFunction // OAuthVersion()
Так же реализуем в виде функций ссылки (URL) для прохождения авторизации:
// Only for internal use. // Function AuthorizeUrl() Export Return "https://appcenter.intuit.com/Connect/Begin"; EndFunction // AuthorizeUrl() // Only for internal use. // Function CallbackUrl() Export Return "https://httpbin.org/forms/post?"; EndFunction // CallbackUrl() // Only for internal use. // Function TokenServerName() Return "https://oauth.intuit.com"; EndFunction // TokenServerName() // Only for internal use. // Function RequestTokenResource() Return "/oauth/v1/get_request_token"; EndFunction // RequestTokenResource() // Only for internal use. // Function AccessTokenResource() Return "/oauth/v1/get_access_token"; EndFunction // AccessTokenResource() // Only for internal use. // Function AppServerName() Return "https://appcenter.intuit.com"; EndFunction // AppServerName() // Only for internal use. // Function AppDisconnectResource() Return "/api/v1/connection/disconnect"; EndFunction // AppDisconnectResource()
Следующий код реализует получение `request token`:
// Only for internal use. // Procedure RequestToken() Export OAuthList = New ValueList(); OAuthList.Add(OAuthNonce(), "oauth_nonce"); OAuthList.Add(OAuthVersion(), "oauth_version"); OAuthList.Add(CallbackUrl(), "oauth_callback"); OAuthList.Add(OAuthTimestamp(), "oauth_timestamp"); OAuthList.Add(OAuthConsumerKey(), "oauth_consumer_key"); OAuthList.Add(OAuthSignatureMethod(), "oauth_signature_method"); Parameters = NewHTTPSecureParameters(); Parameters.HTTPMethod = "GET"; Parameters.ServerName = TokenServerName(); Parameters.Resource = RequestTokenResource(); Parameters.Headers.Insert("Authorization", AuthorizationHeader(Parameters.HTTPMethod, Parameters.ServerName, Parameters.Resource, OAuthList)); Parameters.Fields.Add("oauth_token"); Parameters.Fields.Add("oauth_token_secret"); HTTPSecureRequest(Parameters, EncryptedData); EndProcedure // RequestToken()
Функция NewHTTPSecureParameters описывает структуру параметров запроса.
Методы шифрования в данной статье не рассматриваются и потому, EncryptFields не будет использован.
// Only for internal use. // Function NewHTTPSecureParameters() Export Parameters = New Structure; Parameters.Insert("HTTPMethod"); Parameters.Insert("ServerName"); Parameters.Insert("Resource"); Parameters.Insert("Headers", New Map); Parameters.Insert("Fields", New Array); Parameters.Insert("EncryptFields", New Array); Return Parameters; EndFunction // NewHTTPSecureParameters()
Функция AuthorizationHeader формирует заголовок авторизации.
// Only for internal use. // Function AuthorizationHeader(HTTPMEthod, ServerName, Resource, OAuthList) RequestUrl = StrTemplate("%1%2", ServerName, Resource); Signature = ""; URLEncoding = StringEncodingMethod.URLEncoding; URIStructure = URIStructure(RequestUrl); For Each Parameter In URIStructure.Parameters Do OAuthList.Add(Parameter.Value, Parameter.Key); EndDo; OAuthList.SortByPresentation(); For Each OAuthItem In OAuthList Do If IsBlankString(Signature) Then Signature = Signature + OAuthItem.Presentation + "=" + EncodeString(OAuthItem.Value, URLEncoding); Else Signature = Signature + "&" + OAuthItem.Presentation + "=" + EncodeString(OAuthItem.Value, URLEncoding); EndIf; EndDo; Position = StrFind(RequestUrl, "?"); If Position > 0 Then RequestUrl = Left(RequestUrl, Position - 1); EndIf; OAuthList.Add(OAuthSignature(HTTPMEthod, RequestUrl, OAuthSignatureMethod(), Signature, EncryptedData), "oauth_signature"); Authorization = "OAuth "; For Each OAuthItem In OAuthList Do If StrFind(OAuthItem.Presentation, "oauth_") = 1 Then If Authorization = "OAuth " Then Authorization = Authorization + OAuthItem.Presentation + "=""" + EncodeString(OAuthItem.Value, URLEncoding) + """"; Else Authorization = Authorization + "," + OAuthItem.Presentation + "=""" + EncodeString(OAuthItem.Value, URLEncoding) + """"; EndIf; EndIf; EndDo; Return Authorization; EndFunction // AuthorizationHeader()
Функция URIStructure предназначена для разбора ссылки на составляющие.
// Dissembles URI string and returns it as a structure. // Based on RFC 3986. // // Parameters: // StringURI - String - reference to the resource in the format: // <schema>://<login>:<password>@<host>:<port>/<path>?<parameters>#<anchor> // // Returns: // Structure - with keys: // * Schema - String - schema. // * Login - String - user login. // * Password - String - user password. // * ServerName - String - part : from the StringURI. // * Host - String - host name. // * Port - Number - port number. // * PathOnServer - String - part ?# from the StringURI. // * Parameters - Map - parsed parameters from the StringURI. // Function URIStructure(Val StringURI) Export StringURI = TrimAll(StringURI); Parameters = New Map; // Schema Schema = ""; Position = StrFind(StringURI, "://"); If Position > 0 Then Schema = Lower(Left(StringURI, Position - 1)); StringURI = Mid(StringURI, Position + 3); EndIf; // Connection string and path on the server. ConnectionString = StringURI; PathOnServer = ""; Position = StrFind(ConnectionString, "/"); If Position > 0 Then PathOnServer = Mid(ConnectionString, Position + 1); ConnectionString = Left(ConnectionString, Position - 1); EndIf; // Parameters Position = StrFind(PathOnServer, "?"); If Position > 0 Then ParametersString = Mid(PathOnServer, Position + 1); ParametersArray = StrSplit(ParametersString, "&"); For Each Parameter In ParametersArray Do Position = StrFind(Parameter, "="); If Position > 1 Then Parameters.Insert(Left(Parameter, Position - 1), Mid(Parameter, Position + 1)); EndIf; EndDo; EndIf; // User information and server name. AuthorizeString = ""; ServerName = ConnectionString; Position = StrFind(ConnectionString, "@"); If Position > 0 Then AuthorizeString = Left(ConnectionString, Position - 1); ServerName = Mid(ConnectionString, Position + 1); EndIf; // Login and password. Login = AuthorizeString; Password = ""; Position = StrFind(AuthorizeString, ":"); If Position > 0 Then Login = Left(AuthorizeString, Position - 1); Password = Mid(AuthorizeString, Position + 1); EndIf; // Host and port. Host = ServerName; Port = ""; Position = StrFind(ServerName, ":"); If Position > 0 Then Host = Left(ServerName, Position - 1); Port = Mid(ServerName, Position + 1); For Index = 1 To StrLen(Port) Do Symbol = Mid(Port, Index, 1); If Not IsNumber(Symbol) Then Port = ""; Break; EndIf; EndDo; If IsBlankString(Port) Then If Schema = "http" Then Port = "80"; ElsIf Schema = "https" Then Port = "443"; EndIf; EndIf; EndIf; Result = New Structure; Result.Insert("Schema", Schema); Result.Insert("Login", Login); Result.Insert("Password", Password); Result.Insert("ServerName", ServerName); Result.Insert("Host", Host); Result.Insert("Port", ?(IsBlankString(Port), Undefined, Number(Port))); Result.Insert("PathOnServer", PathOnServer); Result.Insert("Parameters", Parameters); Return Result; EndFunction // URIStructure()
Функция OAuthSignature формирует подпись из переданных данных.
Внимание на реализации функции HMAC_SHA1(UrlSignature, KeySignature) заострять не буду оно довольно тривиальное и достаточно легко реализуется.
Методы шифрования в данной статье не рассматриваются и потому ветка IsBlankString(EncryptNumber) не имеет реализации чтения зашифрованных значений.
Function OAuthSignature(Val HTTPMethod, Val URL, Val OAuthSignatureMethod, Val PreparedSignature, EncryptedData) Export UrlSignature = HTTPMethod + "&" + EncodeString(URL, StringEncodingMethod.URLEncoding) + "&" + EncodeString(PreparedSignature, StringEncodingMethod.URLEncoding); KeySignature = EncryptedFiledValue("oauth_consumer_secret", EncryptedData) + "&" + EncryptedFiledValue("oauth_token_secret", EncryptedData); If Upper(TrimAll(OAuthSignatureMethod)) = "HMAC-SHA1" Then Signature = HMAC_SHA1(UrlSignature, KeySignature); Else ErrorMessage = NStr("en = 'Signature method is not supported.'; |ru = 'Сигнатурный метод не поддерживаеться.'"); Raise ErrorMessage; EndIf; Return Signature; EndFunction // OAuthSignature() // Only for internal use. // Function EncryptedFiledValue(FieldName, EncryptedData) SearchResult = EncryptedData.Find(FieldName, "FieldName"); If SearchResult <> Undefined Then FieldValue = SearchResult.FieldValue; EncryptNumber = SearchResult.EncryptNumber; If IsBlankString(EncryptNumber) Then Return FieldValue; Else Return ""; EndIf; Else Return ""; EndIf; EndFunction // EncryptedFiledValue()
Процедура HTTPSecureRequest, непосредственно, выполняет обращение к серверу авторизации и при успешном результате полученные данные добавляет в таблицу EncryptedData, таблица имеет вид:
Procedure HTTPSecureRequest(Parameters, EncryptedData) Export URIStructure = IHL_CommonUseClientServer.URIStructure( Parameters.ServerName + Parameters.Resource); HTTPRequest = New HTTPRequest(URIStructure.PathOnServer); For Each Header In Parameters.Headers Do HTTPRequest.Headers.Insert(Header.Key, Header.Value); EndDo; HTTPConnection = New HTTPConnection( URIStructure.Host, URIStructure.Port, URIStructure.Login, URIStructure.Password, , , New OpenSSLSecureConnection(Undefined, Undefined)); HTTPResponse = HTTPConnection.CallHTTPMethod(Parameters.HTTPMethod, HTTPRequest); If HTTPResponse.StatusCode = 200 Then ContentType = HTTPResponse.Headers.Get("Content-Type"); If ContentType = "text/plain" Then Body = HTTPResponse.GetBodyAsString(); BodyParts = StrSplit(Body, "&"); For Each BodyPart In BodyParts Do Position = StrFind(BodyPart, "="); If Position > 0 Then FieldName = Left(BodyPart, Position - 1); FieldValue = Mid(BodyPart, Position + 1); If Parameters.Fields.Find(FieldName) <> Undefined Then RowResult = EncryptedData.Find(FieldName, "FieldName"); If RowResult = Undefined Then RowResult = EncryptedData.Add(); EndIf; RowResult.FieldName = FieldName; RowResult.FieldValue = FieldValue; EndIf; EndIf; EndDo; EndIf; Else Raise HTTPResponse.GetBodyAsString(); EndIf; EndProcedure // HTTPSecureRequest()
ШАГ 3. Авторизация пользователя и приложения на QuickBooks
На этом шаге конфигурация «1С:Предприятие 8» должна перенаправить пользователя на ресурс для авторизации на адрес https://appcenter.intuit.com/Connect/Begin?oauth_token=token, где oauth_token равен полученному на предыдущем этапе. После успешной авторизации пользователя и предоставления доступа приложения к компании в QuickBooks, будет выполнено переход на CallbackUrl (вы можете разместить сервер который будет обрабатывать запросы или воспользоваться перехватом управления как выполнено в видео, что ниже).
ШАГ 4. Замена временного токена на постоянный
На последнем шаге необходимо заменить временный oauth_token на постоянный:
- Функция OAuthToken() возвращает временный token полученный на шаге 2;
- Функция OAuthVerifier() возвращает значение oauth_verifier, которое было получено на шаге 3 при переадресации на CallbackUrl:
// Only for internal use. // Procedure AccessToken() Export OAuthList = New ValueList(); OAuthList.Add(OAuthNonce(), "oauth_nonce"); OAuthList.Add(OAuthToken(), "oauth_token"); OAuthList.Add(OAuthVersion(), "oauth_version"); OAuthList.Add(OAuthVerifier(), "oauth_verifier"); OAuthList.Add(OAuthTimestamp(), "oauth_timestamp"); OAuthList.Add(OAuthConsumerKey(), "oauth_consumer_key"); OAuthList.Add(OAuthSignatureMethod(), "oauth_signature_method"); Parameters = NewHTTPSecureParameters(); Parameters.HTTPMethod = "GET"; Parameters.ServerName = TokenServerName(); Parameters.Resource = AccessTokenResource(); Parameters.Headers.Insert("Authorization", AuthorizationHeader(Parameters.HTTPMethod, Parameters.ServerName, Parameters.Resource, OAuthList)); Parameters.Fields.Add("oauth_token"); Parameters.Fields.Add("oauth_token_secret"); HTTPSecureRequest(Parameters, EncryptedData); EndProcedure // AccessToken()
После успешного выполнения необходимо сохранить данные таблицы EncryptedData и в дальнейшем использовать их для формирования AuthorizationHeader для выполнения запросов к ресурсам компании QuickBooks.
Вместо послесловия, полноценную рабочую версию не выкладываю из-за ограниченной лицензии, но мне кажется, не составит большого труда дописать недостающие части. Успехов в начинаниях.
Статья в личном блоге клац
Если не секрет можете рассказать для чего и как используется QuickBooks + 1С
(1) QuickBooks полноценная бухгалтерская система, в свою очередь конфигурация 1С используется как система для складского учета (1C:Small Business). Во многих организациях есть подобные связки бухгалтерия + складская или управленческая система.
(2) у QuickBooks разве нет модуля управление складом?
(3) В «1С:Бухгалтерии» так же есть управление складом, но, как-то, не очень удобно, удобней «1С:Управление торговлей»… В «1С:Бухгалтерии» так же есть возможность учитывать розничные продажи, но, как-то, не очень удобно, удобней «1С:Розница»… и таких примеров масса.
В QuickBooks можно взять в оренду WMS как приложение, тут уже вопрос в цене.
(4) если честно, так до конца и не понятно зачем делать какую-то конфигурацию.
у меня вот какой вопрос: удавалось ли решать вопросы единой аутентификации для всех систем, как в примере выше? QuickBooks + 1С.
затык заключается, в том, что openID 1C можно аутентифицироваться только в системах 1С, а хотелось бы иметь доступ во все платформы.
(5) если говорить об OpenID, то у стандарта всегда есть 3 стороны, в данном случае QuickBooks выступает как провайдер, клиент как клиент зарегистрированный у провайдера, 1С конфигурация как приложение, которое получает доступ к данным.
Для других систем так же можно пройти авторизацию, если провайдер поддерживается этими системами.
Аутентификацию к различным системам есть возможность выполнить с помощьюhttps://infostart.ru/public/560516/ версии выше чем 0.9.0 (которая пока не доступна, но готовится к релизу, а пока делается описание, шаблоны для того чтобы выложить встраиваемую конфигурацию на общее обозрение).
(6) у нас потребность не получении данных, в том, чтобы выполнив вход в провайдере (который не на 1С) попасть в систему 1С как в приложение.
(7) То что вы спрашиваете это не тема этой статьи, совсем. Но возможность такое сделать есть. Подали мне идею для платного модуля 🙂
(8) с вас 40% 🙂
Здравствуйте! в какой программе реализуется весь функционал? где писать код?
(10) Добрый день, все реализуется в «1С:Предприятие 8», после прохождения авторизации можно использовать все команды из API QuickBooks.
(11) День добрый, вопрос: я как понимаю интеграция заключается в следующем: склад и всё что с ним связанно оформляется в 1С, а фин.операции оформляются в QuickBooks?
(12) Все зависит от того, какая конфигурация выбрана за основу и на сколько тесной необходима интеграция.
Ни где не нашел листинг процедуры из общего модуля: IHL_CommonUseClientServer.URIStructure
Можете привести?
(14)https://github.com/FoxyLinkIO/FoxyLink/blob/develop/src/CommonModules/FL_Co mmonUseClientServer/Ext/Module.bsl
(15) Благодарю, очень любезно с вашей стороны. Очень полезный экспириенс.
Добрый Вечер! Опишите сам процесс подключения к песочнице.
Не совсем понятно, как ориентироваться на сайте, и какие поля там понадобятся в подключении. Спасибо
(17) About 18 months ago, we announced support for OAuth 2.0. At that time, all new apps started using OAuth 2.0 for authorization, and (optionally) OpenID Connect for single sign-on authentication.
We also launched an open OAuth 2.0 migration beta program so developers could give us feedback on our tools and resources. Over the past year, we have provided several OAuth 2.0 libraries, and included modules to migrate OAuth 1.0 tokens to OAuth 2.0 including, most recently, when we released libraries in Python and Node.js.
Если кратко, статья уже не актуальна. Уже OAuth 2.0, там все проще и проблем не должно возникать
(18)
Боюсь, что вы не до оцениваете ценность вашей статьи.
Мне, например, не к QuickBooks подключаться нужно.
И метод сигнатур другой используется.
Но есть еще куча ресурсов, которые по прежнему пользуют oauth1.0a( как в моем случае).
По этом ваша публикация очень хороший и наглядный пример для меня. 🙂
У меня вот к вам другой вопрос, Петр.
скажите, порядок выполнения запросов для авторизации(request token ->access token) всегда остается один и тот же независимо от oauth_signature_method ?
Просто в моем случае это private app и RSA-SHA1. То есть, на сайт выложен public key а у меня есть private key.
Или в таком случае уже не нужно запрашивать токен доступа? можно сразу выполнять запрос к данным передавая секретный ключ(oauth_token_secret) в заголовке?
Конечно, подключение к QuickBooks oline большая проблема для меня, и эта статься единственное от чего я отталкивался за все это время, правда все безрезультатно.
Теперь, у меня возник вопрос к подключению QuickBooks Desktop, вот тут на самом деле все проще должно быть, но нигде нет примеров подключения на встроенном языке 1С.
qbsdk130.exe я уже установил, хочу знать как запускать его программно из модуля 1с
(21) пробуйте ЗапуститьПриложение
(20) Пришлите ссылку на API, я вам порядок в личку распишу.
(22)Добрый день и С новым годом! Спасибо за предложенный вариант, но у меня нет необходимости открывать демонстративно qbsdk130.exe в 1С.
Мне нужно пользоваться инструменты qbsdk130.exe для интеграции. Как подключаться к qbsdk130.exe из модуля 1С, мне не известно, в интернете нет ни каких примеров(
(24)
Не понимаю о чем речь. вообще. пишите в личку. если интересно
(25)Да, интересно. Напишите мне первым, у меня нет возможности до Вас дописаться
Добрый день! Вы у меня в друзьях, а написать Вам не могу. Напишите мне первым, пожалуйста. У меня один вопросик будет
(27) Добрый день! Получилось ли у Вас подключиться к QuickBooks Online?