Веб-сервисы компании FlySeason.

(Релиз от 04 апреля 2012 года)

Введение.

FlySeason предоставляет всем туристическим агентствам (и другим заинтересованным фирмам) ряд web-сервисов для доступа к своей базе чартерных авиабилетов (с правом чтения и записи данных в базу):

Вы можете вычитать нашу базу чартерных авиабилетов и без регистрации, однако это не имеет смысла - ибо пройдя процедуру регистрации вы получите скидку на каждый наш проданный билет. Регистрация проводится по телефону +7 (495) 648 80 88 и занимает несколько минут. Кроме того, пройдя процедуру регистрации вы получите право пополнять нашу базу чартерных авиабилетов своими собственными билетами.

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

Воспользовавшись нашими web-сервисами, вы сможете выводить билеты из нашей базы в общем списке билетов на своем сайте (в любом нужном вам дизайне). Вы также можете (в дополнение к нашей web-админке) сделать свое собственное полноценное приложение для отслеживания состояния ващих заказов. В-общем, мы постарались сделать наши web-сервисы простыми, мощными и удобными - присоединяйтесь!

Механизм подключения.

Подключение осуществляется с помощью обычного SOAP/WSDL сервиса. В простейшем случае никаких клиентских сертификатов и других осложнений не требуется. Вы просто ставите ссылку в любой современной среде программирования на наши сервисы - ваша IDE автоматически создаcт вам прокси-сервера для обращения к нашим сервисам и вы можете писать/читать нашу базу чартерных авиабилетов.

Совместимость.

Сервисы написаны на NET и самым тщательным образом протестированы со всеми известными кроссплатформенными тестовыми клиентами - Altova XmlSpy, SoapUI и другими. Во время разработки тщательно тестировалась также совместимость с платформами Flex и Java.

Поддержка.

Во всех случаях, когда вам необходима техподдержка - она будет вам оказана (только зарегистрированным клиентам с логином). Для этого вам надо обратится по реквизитам хостинга //www.vb-net.com/ (предпочтительно Skype, однако в срочных случаях техподдержка может быть оказана и по телефону).

Подключение с открытым и зашифрованным паролем.

Если вы подключаетесь к нашим сервисам со своего сервера (или с доверенного компьютера в вашем офисе) и нет опасений что ваш логин/пароль будет перехвачен администраторами на интернет-магистралях - вы можете работать с открытым логином и паролем (не затрудняя себя криптографией). Однако если вы планируете подключатся к нашим сервисам из jQuery, Flex, Java applet, а также из любых десктопных приложений, которые будут устанавливаться на неизвестных вам компьютерах - шифровать свой пароль обязательно. Иначе с помощью Firebug или любого сетевого снифера любой (даже малоквалифицированный) пользователь увидит ваши логины/пароли и начнет пополнять нашу базу мусором от вашего имени.

Соответственно, при регистрации у менеджеров компании - вы получаете либо только логин/пароль либо еще дополнительно ключ и IV симметричного алгоритма AES, которые выглядят примерно так:


AES Key: 8AF6F523F8EAFF0A494BC99ED4F97F9F
AES IV : 564D9065D131C91C539353D1C87C8E33

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

На скринах ниже вы видите два запроса от реального клиента наших сервисов - первый запрос открытый (GetTicketDay) и не требует вообще логина/пароля, следующий запрос клиентское приложение выдало требующее логина/пароля (GetOneTicketDilerInfo) - как вы видите, поле Password зашифровано. Подделать это поле практически невозможно (в пределах устойчивости криптоалгоритма AES), к тому же оно постоянно меняется. Обратите также внимание, что ответы (сведения о билетах) и все прочие поля запросов даются открытым текстом. Поле Password в структуре Login - это единственное защищенное поле в протоколе общения клиентcких приложений с нашими сервисами.



Обфускация чуствительных данных.

В текущем релизе не было завершено тестирование механизма IKE - получения и обмена ключами AES. Поэтому ключи AES вы должны получать у Менеджеров и зашивать их в свой софт. В следущей версии вы будете получать ключи шифрования с сервера по алгоритму ECDH.

В связи с тем, что в текущем релизе ключи статически зашиваются в код - хотелось бы обратить внимание на недопустимость прямого указания ключей, логинов и паролей в виде литеральных текстовых констант. Иначе для взлома ваших секретных данных не нужно даже отладчика - достаточно простого просмотра текста вашего клиента любым известным бесплатным инструментом. Ниже вы видите, как заметны секретные ключи шифрования в готовом SWF-файл, размещенном в браузере. Какой смысл во всей защите, если ключи шифрования прописаны в коде обычным текстом? Ведь любому программисту достаточно взять эти ключи, написать ровно пять строчек кода и начать набивать нашу базу мусором от вашего имени.



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

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

Проще всего произвести подключение с помощью XmlSpy или SoapUI. В случае подключения с открытым паролем ваше подключение может выглядеть вот так:




   1:  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://service.flyseason.ru/" xmlns:fly="http://schemas.datacontract.org/2004/07/FlyService">
   2:     <soapenv:Header/>
   3:     <soapenv:Body>
   4:        <ser:GetOneTicketDilerInfo>
   6:           <ser:TicketID>
   7:              <fly:GUID>3d89d535-2fd8-46f4-80e3-c0a47bd13964</fly:GUID>
   8:           </ser:TicketID>
  10:           <ser:Login>
  11:              <fly:Username>ВашЛогин</fly:Username>
  12:              <fly:Password>ВашПароль</fly:Password>
  13:              <fly:CryptoMode>false</fly:CryptoMode>
  14:           </ser:Login>
  15:        </ser:GetOneTicketDilerInfo>
  16:     </soapenv:Body>
  17:  </soapenv:Envelope>

Пожалуйста, не пихайте эти XML-файлы методом POST в наши SOAP/WSDL сервисы - они даны лишь как образцы правильного подключения. Если вы начинающий программист и вам не вполне понятна разница между SOAP/WSDL-сервисами и RAW XML передаваемыми методом POST - почитаете следующее разъяснение.

Ответ сервиса:


   1:  <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   2:     <s:Body>
   3:        <GetOneTicketDilerInfoResponse xmlns="http://service.flyseason.ru/">
   4:           <GetOneTicketDilerInfoResult xmlns:a="http://schemas.datacontract.org/2004/07/FlyService" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
   5:              <a:i>18330</a:i>
   6:              <a:ID>3d89d535-2fd8-46f4-80e3-c0a47bd13964</a:ID>
   7:              <a:ReturnID>94a9c60f-2b5b-4d0e-b274-81495d42cb5f</a:ReturnID>
   8:              <a:CrDate>27.03.2012</a:CrDate>
   9:              <a:Special>1</a:Special>
  10:              <a:FromCountry>Россия</a:FromCountry>
  11:              <a:FromCity>Москва</a:FromCity>
  12:              <a:FromAirport>DME</a:FromAirport>
  13:              <a:ToCountry>Турция</a:ToCountry>
  14:              <a:ToCity>Анталия</a:ToCity>
  15:              <a:ToAirport>AYT-1</a:ToAirport>
  16:              <a:FromDate>01.04.2012</a:FromDate>
  17:              <a:FromTime>14:00</a:FromTime>
  18:              <a:FlyTime>15:00</a:FlyTime>
  19:              <a:AviaCompany i:nil="true"/>
  20:              <a:AviaCompanyCode>LLM</a:AviaCompanyCode>
  21:              <a:FlyNumber>9355</a:FlyNumber>
  22:              <a:FlyClass>Economy</a:FlyClass>
  23:              <a:Price>165.0000</a:Price>
  24:              <a:HowMany>есть</a:HowMany>
  25:              <a:AirTransfer>Нет</a:AirTransfer>
  26:              <a:AirTransferComment>-0</a:AirTransferComment>
  27:           </GetOneTicketDilerInfoResult>
  28:        </GetOneTicketDilerInfoResponse>
  29:     </s:Body>
  30:  </s:Envelope>

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


   1:  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://service.flyseason.ru/" xmlns:fly="http://schemas.datacontract.org/2004/07/FlyService">
   2:     <soapenv:Header/>
   3:     <soapenv:Body>
   4:        <ser:GetOneTicketDilerInfo>
   6:           <ser:TicketID>
   7:              <fly:GUID>3d89d535-2fd8-46f4-80e3-c0a47bd13964</fly:GUID>
   8:           </ser:TicketID>
  10:           <ser:Login>
  11:              <fly:Username>DDD</fly:Username>
  12:              <fly:Password>7098DA9865A0CAFDD9D338108ADD627AC2DD94BA42697C210490CDAEF53E88339ACF02375AEF5BD6DABFE621B1E2C297C91F91C6B19ABAF30439CE615625EFDF50A0D74078CB602670234875967FC3E41543BF54CF5CE821EDA7BF3432ADB172893D43CC5108EB89B637287968E9BAF83921ADB8938EAEE3A631919B484D2782</fly:Password>
  13:              <fly:CryptoMode>true</fly:CryptoMode>
  14:           </ser:Login>
  15:        </ser:GetOneTicketDilerInfo>
  16:     </soapenv:Body>
  17:  </soapenv:Envelope>

Вы можете обратится с этим паролем из общепризнанных тестовых клиентов с GUI:



Обратите внимание, что SoapUI специально сделан так, чтобы можно было в целях тестирования вручную указать несоответствующие протоколу данные. Из своего программного клиента, созданного в вашей среде программирования - вы не сможете указать произвольные данные например вместо GUID.

На следующих двух скринах вы видите механизм работы SOAP/WSDL-сервиса - сначала клиентское приложение вычитало все параметры обмена из WSDL а затем оно выполнило обращение к сервисам (в строгом соответствии со спецификацией WSDL). Например если в параметре обращения требуется ID-билета, то невозможно подсунуть в параметры обращения к сервсиам что-нибудь не соответствующее GUID (не соответствующее регулярному выражению [\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}) - этого не позволит клиентскому приложению ни его среда программирования, ни протокол обмена. Несоответствующие WSDL данные, даже не поступят на обработку к нашим сервисам и, соответственно, протокол отмена не пропустит к вам обратно никакого неожиданного ответа, не указанного в WSDL.


Код движка шифрования.

Код механизма шифрования наших сервисов открыт и вы можете увидеть его ниже. Везде где для доступа к нашей баз необходим логин/пароль используется структура данных AU, и нижеследующие фрагменты кода:

   1:  <DataContract()>
   2:  Public Class AU
   3:   
   4:      <DataMember(IsRequired:=True, Order:=1)>
   5:      Public Username As String
   6:   
   7:      <DataMember(IsRequired:=True, Order:=2)>
   8:      Public Password As String
   9:   
  10:      <DataMember(IsRequired:=True, Order:=3)>
  11:      Public CryptoMode As Boolean
  12:   
  13:  End Class

Ниже вы можете видеть режимы работы алгоритма AES - 16 байтный ключ и IV, режим заполнения PKCS7:


   1:  Public Class Cryptor
   2:   
   3:      Dim AES As System.Security.Cryptography.Aes
   4:   
   5:      Public Sub New(ByVal UserName As String)
   6:          Try
   7:              Dim db1 As New DB.FlySeasonDataContext
   8:              Dim CurDiler = (From X In db1.Diler Select X Where X.Login = UserName).ToList
   9:              If CurDiler IsNot Nothing Then
  10:                  If CurDiler.Count > 0 Then
  11:                      AES = New System.Security.Cryptography.AesCryptoServiceProvider
  12:                      AES.KeySize = 128
  13:                      AES.BlockSize = 128
  14:                      AES.Padding = System.Security.Cryptography.PaddingMode.PKCS7
  15:                      AES.Key = CurDiler(0).RijndaelKey.ToArray
  16:                      AES.IV = CurDiler(0).RijndaelIV.ToArray
  17:                  End If
  18:              End If
  19:          Catch ex As Exception
  20:              Throw New Exception("No cryptographic key")
  21:          End Try
  22:      End Sub
  23:   
  24:      Public Function DeCryptBytesAes(ByVal CryptoBytes As Byte()) As String
  25:          Try
  26:              Dim CryptoTransform As System.Security.Cryptography.ICryptoTransform = AES.CreateDecryptor()
  27:              Dim TstKey As String = Common.ByteArrToString(AES.Key)
  28:              Dim TstIV As String = Common.ByteArrToString(AES.IV)
  29:              Dim TstCripto As String = Common.ByteArrToString(CryptoBytes)
  30:              Dim ClearBytes As Byte() = CryptoTransform.TransformFinalBlock(CryptoBytes, 0, CryptoBytes.Length)
  31:              Return System.Text.Encoding.UTF8.GetString(ClearBytes)
  32:          Catch ex As System.Security.Cryptography.CryptographicException
  33:              Return ""
  34:          End Try
  35:      End Function
  36:   
  37:  ...
  38:   
  39:  End Class

Структура поля Password.

Поле Password структуры AU это не то же самое, что логин/пароль, которые вы получили у менеджеров компании. Это строка сложной структуры, код обработки которой вы видите ниже:

   1:      Public Shared Function GetPassword(ByVal Login As AU) As String
   2:          'расшифровать пароль и выделить в нем первый сегмент
   3:          If Login.Password IsNot Nothing Then
   4:              If Login.CryptoMode Then
   5:                  Dim X As New Cryptor(Login.Username)
   6:                  Dim Buf() As Byte = StringToByteArr(Login.Password)
   7:                  Dim TST1 As String = ByteArrToString(Buf)
   8:                  Dim RawPassword As String = X.DeCryptBytesAes(Buf)
   9:                  If RawPassword IsNot Nothing Then
  10:                      If RawPassword.Length > 0 Then
  11:                          Dim Pos1 As Integer = RawPassword.IndexOf("###")
  12:                          If Pos1 > 0 Then
  13:                              Return Left(RawPassword, Pos1)
  14:                          Else
  15:                              Return RawPassword
  16:                          End If
  17:                      End If
  18:                  End If
  19:              Else
  20:                  Dim Pos1 As Integer = Login.Password.IndexOf("###")
  21:                  If Pos1 > 0 Then
  22:                      Return Left(Login.Password, Pos1)
  23:                  Else
  24:                      Return Login.Password
  25:                  End If
  26:              End If
  27:          End If
  28:      End Function

Как вы видите, в качестве пароля после расшифровки рассматривается только левая часть строки (до символов '###') поэтому чтобы осложнить злоумышленникам жизнь - даже при простых запросах информации о билетах - вы можете добавлять к вашему текстовому паролю произвольную текстовую строку - GUID или TimeStamp.

При записи в базу своих билетов - поле пароль фактически является еще и цифровой подписью. Вторым сегментом пароля является сериализованный в строку MD5-хеш всех ваших записываемых данных, а третьим сегментом (обеспечивающим постоянно разный видимый хакеру пароль) является произвольный GUID или TimeStamp.

Таким образом, при записи билетов (и заказов) к нам в базу поле Password структуры AU имеет примерно следующий вид:

ЯВася###7BDEBE65C72806D3422C0523B937F250###455DF576-2346-4C54-9B31-FCFEDD008A5A

Что превращается вашей программой после шифрования полученным вами ключом в 'почти' случайный (и постоянно меняющийся) набор байтов, подобный такому:

90DB5F018BB552EBA9604B88BE5F4889A317F1A1DF8C4782A76E5D74E1183B20A4B8B38AD1FF154CD947D585276BC294D78BC6734C6AACFC2E3E965B5951FF78C6FA87518D381B13B5FE466AFE05F392

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

Обратите пожалуйста внимание, что второй и третий сегмент (MD5-хеш и произвольный GUID/TimeStamp) перед шифрованием не используются напрямую в виде бинарных структур вашей среды программирования, а сериализуются в символы. Пожалуйста, обратите внимание так же на то, что и как в случае работы с открытым паролем и цифровой подписью (на доверенных компьютерах), так и в случае шифрованного пароля/цифровойподписи - вы передаете не бинарные структуры, а обычные символы.

Тестирование цифровой подписи.

При загрузке данных в базу - билетов, цен и заказов - вы должны заверить все передаваемые данные MD5-хешем. Это необходимо чтобы к нам в базу не загружался всякий мусор. В режиме с открытой передачей пароля (при работе с доверенного компьютера в офисе) - этот хеш передается в открытом виде и может быть подделан любым школьником. Однако для простоты вашего подключения этот режим сохранен. При работе вашего софта на базе сервисов FlySeason на недоверенных компьютерах - когда пароль и цифровая подпись видны в Firebug и WireShark - открытая передача этих данных недопустима - и пароль и MD5 запаковываются и шифруются алгоритмом AES (как показана в предыдущем разделе).

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

У сервиса http://service.flyseason.ru/Upload.svc?wsdl есть метод GetTicketMD5, которому вы можете передать структуру данных OneTicket(описанную ниже в разделе описания сервиса Upload) и получить MD5-хеш - как он проверяется на сервере. Ваш софт должен формировать MD5-хеш для цифровой подписи точно так же.

Ниже вы видите код тестового сервиса на сервере, механизм его работы - а также скрин тестового обращения к этому сервису.

   1:  Public Class Upload
   2:      Implements IUpload
   3:   
   4:      Public Function GetTicketMD5(ByVal OneTicket As OneUploadTicket) As String Implements IUpload.GetTicketMD5
   5:          Dim SB As New StringBuilder
   6:          SB.Append(OneTicket.ReturnID)
   7:          SB.Append(OneTicket.FromCountry)
   8:          SB.Append(OneTicket.FromCity)
   9:          SB.Append(OneTicket.FromAirport)
  10:          SB.Append(OneTicket.ToCountry)
  11:          SB.Append(OneTicket.ToCity)
  12:          SB.Append(OneTicket.ToAirport)
  13:          SB.Append(OneTicket.FromDate)
  14:          SB.Append(OneTicket.FromTime)
  15:          SB.Append(OneTicket.FlyTime)
  16:          SB.Append(OneTicket.AviaCompanyCode)
  17:          SB.Append(OneTicket.FlyNumber)
  18:          SB.Append(OneTicket.FlyClass)
  19:          SB.Append(OneTicket.Price)
  20:          SB.Append(OneTicket.HowMany)
  21:          SB.Append(OneTicket.AirTransfer)
  22:          SB.Append(OneTicket.AirTransferComment)
  23:          SB.Append(OneTicket.Suplier)
  24:          Dim Buf() As Byte = System.Text.Encoding.UTF8.GetBytes(SB.ToString)
  25:          Dim MD5 As System.Security.Cryptography.MD5 = System.Security.Cryptography.MD5.Create
  26:          Return Common.ByteArrToString(MD5.ComputeHash(Buf))
  27:      End Function
  28:   
  29:  ...
  30:   
  31:  End Class



Код проверки цифровой подписи.

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

Ниже вы видите не просто код тестового хандлера (демонстрирующий лишь принцип расчета MD5) а один из ключевых фрагментов кода криптографического модуля наших web-сервисов:


   1:  Public Class Common
   2:   
   3:   Shared Function CheckeMD5internal(ByVal Login As AU, ByVal FullData As String) As Boolean
   4:          CheckeMD5internal = False 'чудес не бывает
   5:          If FullData IsNot Nothing Then
   6:              If Login.CryptoMode Then
   7:                  Dim RJ As New Cryptor(Login.Username)
   8:                  Dim Buf() As Byte = StringToByteArr(Login.Password)
   9:                  Dim TST1 As String = ByteArrToString(Buf)
  10:                  Dim RawPassword As String = RJ.DeCryptBytesAes(Buf).Trim
  11:                  If RawPassword IsNot Nothing Then
  12:                      If RawPassword.Length > 0 Then
  13:                          Dim Pos1 As Integer = RawPassword.IndexOf("###")
  14:                          If Pos1 > 0 Then
  15:                              Dim Password As String = Left(RawPassword, Pos1)
  16:                              'первый фрагмент - пароль, проверяем его по базе
  17:                              Dim db1 As New DB.FlySeasonDataContext
  18:                              Dim CurDiler = (From Y In db1.Diler Select Y Where Y.Login = Login.Username And Y.Password = Password).ToList
  19:                              If CurDiler IsNot Nothing Then
  20:                                  If CurDiler.Count > 0 Then
  21:                                      'указанный в шифрованном пакете пароль оказался легальным
  22:                                      Dim ClientMD5 As String = GetMD5(RawPassword)
  23:                                      If ClientMD5 <> "" Then
  24:                                          Dim MD5 As System.Security.Cryptography.MD5 = System.Security.Cryptography.MD5.Create
  25:                                          Dim Data As Byte() = System.Text.Encoding.UTF8.GetBytes(FullData)
  26:                                          Dim ServerMD5 As String = Common.ByteArrToString(MD5.ComputeHash(Data))
  27:                                          If ServerMD5.ToLower = ClientMD5.ToLower Then
  28:                                              'чудо произошло - MD5 переданный с клиента и расчитанный на сервере - совпали
  29:                                              Return True
  30:                                          End If
  31:                                      End If
  32:                                  End If
  33:                              End If
  34:                          End If
  35:                      End If
  36:                  End If
  37:              End If
  38:          End If
  39:      End Function
  40:   
  41:      Public Shared Function ConcatParm(Of T)(ByVal Parm As T) As String
  42:          'конкатенация в строку всех свойств сложных объектов OneTouristInfo, OneZakazInfo, TourustZakaz, UploadReturnPrice
  43:          Dim Ret As New StringBuilder
  44:          For i As Integer = 0 To Parm.GetType.GetFields.Count - 1
  45:              Ret.Append(Parm.GetType.GetFields(i).ToString)
  46:          Next
  47:          Return Ret.ToString
  48:      End Function
  49:   
  50:  #Region "Полиморфные обвязки вокруг основного алгоритма и функции с дженериком."
  51:   
  52:      Public Shared Function CheckMD5(ByVal Login As AU, ByVal GUID As System.Guid) As Boolean
  53:          Return CheckeMD5internal(Login, GUID.ToString)
  54:      End Function
  55:   
  56:      Public Shared Function CheckMD5(ByVal Login As AU, ByVal FromGUID As System.Guid, ByVal ToGUID As System.Guid) As Boolean
  57:          Return CheckeMD5internal(Login, FromGUID.ToString & ToGUID.ToString)
  58:      End Function
  59:   
  60:      Public Shared Function CheckMD5(ByVal Login As AU, ByVal OneTourist As OneTouristInfo) As Boolean
  61:          Return CheckeMD5internal(Login, ConcatParm(Of OneTouristInfo)(OneTourist))
  62:      End Function
  63:   
  64:      Public Shared Function CheckMD5(ByVal Login As AU, ByVal OneZakaz As OneZakazInfo) As Boolean
  65:          Return CheckeMD5internal(Login, ConcatParm(Of OneZakazInfo)(OneZakaz))
  66:      End Function
  67:   
  68:      Public Shared Function CheckMD5(ByVal Login As AU, ByVal OneTouristZakaz As TourustZakaz) As Boolean
  69:          Return CheckeMD5internal(Login, ConcatParm(Of TourustZakaz)(OneTouristZakaz))
  70:      End Function
  71:   
  72:      Public Shared Function CheckMD5(ByVal Login As AU, ByVal OneTicket As OneUploadTicket) As Boolean
  73:          Return CheckeMD5internal(Login, ConcatParm(Of OneUploadTicket)(OneTicket))
  74:      End Function
  75:   
  76:      Public Shared Function CheckMD5(ByVal Login As AU, ByVal OnePrice As UploadReturnPrice) As Boolean
  77:          Return CheckeMD5internal(Login, ConcatParm(Of UploadReturnPrice)(OnePrice))
  78:      End Function
  79:   
  80:  #End Region
  81:   
  82:      Shared Function GetMD5(ByVal RawPassword As String) As String
  83:          'простой строчный разбор - выделить 32 символа из второго сегмента пароля 
  84:          If RawPassword IsNot Nothing Then
  85:              If RawPassword.Length > 0 Then
  86:                  Dim Pos1 As Integer = RawPassword.IndexOf("###")
  87:                  If Pos1 > 0 Then
  88:                      Dim Pos2 As Integer = RawPassword.IndexOf("###", Pos1 + 1)
  89:                      If (Pos2 > 0) And (Pos2 - Pos1) = 35 Then
  90:                          'MD5 задан так: Pass ### MD5 ### Trash
  91:                          Return Mid(RawPassword, Pos1 + 4, 32)
  92:                      ElseIf (Pos2 = -1) And (RawPassword.Length - Pos1 - 3 - 32 = 0) Then
  93:                          'MD5 задан так: Pass ### MD5
  94:                          Return Mid(RawPassword, Pos1 + 4, 32)
  95:                      End If
  96:                  End If
  97:              End If
  98:          End If
  99:      End Function
 100:   
 101:      Public Shared Function ByteArrToString(ByVal Arr As Byte()) As String
 102:          Dim ArrBuilder = New System.Text.StringBuilder
 103:          For j As Integer = 0 To Arr.Length - 1
 104:              ArrBuilder.AppendFormat("{0:X2}", Arr(j))
 105:          Next
 106:          Return ArrBuilder.ToString
 107:      End Function
 108:   
 109:      Public Shared Function StringToByteArr(ByVal Str As String) As Byte()
 110:          Dim Arr(Str.Length / 2 - 1) As Byte, OneByte As Byte
 111:          For I As Integer = 0 To Str.Length / 2 - 1
 112:              OneByte = Byte.Parse(Str.Substring(I * 2, 2), Globalization.NumberStyles.AllowHexSpecifier)
 113:              Arr(I) = OneByte
 114:          Next
 115:          Return Arr
 116:      End Function
 117:   
 118:  ....
 119:   
 120:  End Class

Описание сервиса http://service.flyseason.ru/CursUSD.svc?wsdl

Этот сервис предназначен для получения курса валюты (USD) по которому работает FlySeason. Все цены во всех сервисах вы получите либо в долларах США либо в Евро (в зависимости от MoneyType - который при загрузке прайсов и цен определяется по наличию символа "E" в ключевом поле, а при выводе билетов и цен по полю MoneyType) и чтобы пересчитать их в рубли - вам надо умножить цену на курс валюты (который вы как и получите этим сервисом).

Сервис содержит единственный метод без параметров и не требует аутентификации:

  • CursUSD

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




Описание сервиса http://service.flyseason.ru/CursEUR.svc?wsdl

Этот сервис предназначен для получения курса валюты (EUR) по которому работает FlySeason. Все цены во всех сервисах вы получите либо в долларах США либо в Евро (в зависимости от MoneyType - который при загрузке прайсов и цен определяется по наличию символа "E" в ключевом поле, а при выводе билетов и цен по полю MoneyType) и чтобы пересчитать их в рубли - вам надо умножить цену на курс валюты (который вы как и получите этим сервисом).

Сервис содержит единственный метод без параметров и не требует аутентификации:

  • CursUSD

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




Описание сервиса http://service.flyseason.ru/CityCountry.svc?wsdl

Сервис CityCountry является открытым сервисом, не требует аутентификации. Он сообщает по каким странам и городам у нас есть билеты на чартерные рейсы. Основное назначение этого сервиса - дать вам правильные наименования стран и городов в нашей базе. Все запросы к другим сервисам вы будете строить с этими наименованиями.

  • GetCountry - не требует параметров. Выдает список стран.
  • GetCity - принимает в качестве параметра страну и выдает список городов.



Описание сервиса http://service.flyseason.ru/TicketList.svc?wsdl

Сервис TicketList содержит несколько методов для обзора и отбора билетов на чартерные рейсы в нашей базе. Все методы кроме GetOneTicketDilerInfo полностью открыты для всех желающих и доступны без логина и пароля. Но, как уже отмечалось выше, с логином и паролем вы можете получить цену не для конечного пользователя, а со скидкой - поэтому отбор билетов методом GetOneTicketInfo без логина и пароля - во многом экономически бессмысленное занятие и имеет смысл только в процессе тестирования.

  • GetDateTimeFormat - это тестовый сервис, параметров при обращении не требует. Выдает способ сериализации даты в строку. Именно в этом формате мы принимаем даты по всему проекту.
  • GetTicketDay - дает вам календарный план чатрерных рейсов на месяц. Этому методу вы задаете начальную дату, город вылета и город прилета - получаете список дней, когда рейсы есть.
  • GetGoodTickets - принимает в качестве параметра Request (структуру которого вы видите ниже) и выдает список GUID с номерами билетов точно соответствующими запросу.
  • GetLessTickets - принимает в качестве параметра Request и выдает список GUID с номерами билетов даты которых отличаются в меньшую сторону от запроса.
  • GetMoreTickets - принимает в качестве параметра Request и выдает список GUID с номерами билетов даты которых отличаются в большую сторону от запроса.
  • GetOnlySpecialGoodTickets - принимает в качестве параметра Request и выдает список GUID с номерами билетов-спецпредложений даты которых точно соответствующими запросу.
  • GetOnlySpecialLessTickets - принимает в качестве параметра Request и выдает список GUID с номерами билетов-спецпредложений даты которых отличаются в меньшую строну от запроса.
  • GetOnlySpecialMoreTickets - принимает в качестве параметра Request и выдает список GUID с номерами билетов-спецпредложений даты которых отличаются в большую строну от запроса.
  • GetWithoutSpecialGoodTickets - принимает в качестве параметра Request и выдает список GUID с номерами билетов точно соответствующими запросу (без специпредложений).
  • GetWithoutSpecialLessTickets - принимает в качестве параметра Request и выдает список GUID с номерами билетов даты которых отличаются в меньшую сторону от запроса (без специпредложений).
  • GetWithoutSpecialMoreTickets - принимает в качестве параметра Request и выдает список GUID с номерами билетов даты которых отличаются в большую сторону от запроса (без специпредложений).
  • GetOneTicketInfo - передав этому сервису GUID с номером билета вы получите полную инфомацию о билете.
  • GetOneTicketDilerInfo - передав этому сервису GUID с номером билета вы получите полную инфомацию о билете (пример смотри в разделе Тестирование подключения с паролем и без). Этот сервис в отличии от сервиса GetOneTicketInfo требует логина/пароля и выдает цену с учетом скидки, которую вам дал менеджер фирмы при регистрации.

   1:  <DataContract()>
   2:  Public Class TicketRequest
   3:   
   4:      <DataMember(IsRequired:=True)>
   5:      Public Property FromCountry As String
   6:      <DataMember(IsRequired:=True)>
   7:      Public Property FromCity As String
   8:      <DataMember(IsRequired:=True)>
   9:      Public Property ToCountry As String
  10:      <DataMember(IsRequired:=True)>
  11:      Public Property ToCity As String
  12:      <DataMember(IsRequired:=True)>
  13:      Public Property FromDate As String
  14:      <DataMember(IsRequired:=True)>
  15:      Public Property ToDate As String
  16:      <DataMember(IsRequired:=True)>
  17:      Public Property OneWay As Boolean
  18:   
  19:  End Class




Описание сервиса http://service.flyseason.ru/Upload.svc?wsdl

Этот сервис предназначен для загрузки ваших чартерных билетов в нашу базу. Сервис состоит из следующих методов:

Все сервисы загрузки билетов в базу (кроме GetDateTimeFormat, GetAviaCompanyCode и GetTicketMD5) работают с аутентификацией и принимают в качестве одного из параметров структуру AU, которая описана выше. В структуре AU вы указываете флаг CryptoMode - работаете ли вы с открытым паролем/цифровойподписью или с зашифрованным.

Пять серввисов - AddTicket, AddReturnPrice, DelTicket, DelReturnPrice, SetSpecialTicket - требуют в структуре AU (в поле Password) цифровой подписи в виде MD5-хеша.

  • GetDateTimeFormat - то же, что и выше. Добавлена для удобства тестирования вашего подключения к этому сервису.
  • GetAviaCompanyCode - выдает список наименований авиакомпаний и их кодов. При всех добавлениях билетов в базу вы должны использовать коды авиакомпаний, полученный этим сервисом. Правильный код авиакомпании по нашей базе имеет большое значение для расчета цены билета (особенно когда прямой и обратный билет заказываются сразу в одной и той же компании). Метод открыт не имеет параметров и не требует аутентификации.
  • GetTicketMD5 - тестовый сервис, которым вы можете убедится в корректности расчета MD5-хеша. Принимает на вход структуру OneTicket. Описание см в разделе - тестирование цифровой подписи.
  • AddTicket - этот сервис принимает в качестве параметра структуру данных OneUploadTicket и добавляет ваш билет в нашу базу. При добавлении билета есть одна хитрость с ценой. Вы можете напрямую указать в билете цену, но как правило в цене билета указывается ноль, а цены загружаются в отдельную таблицу ReturnPrice методом AddReturnPrice. В качестве Supplier в базу записывается ваш логин. Метод возвращает ID загруженного в базу билета.

  •    1:  <DataContract()>
       2:  Public Class OneUploadTicket
       3:   
       4:      <DataMember(IsRequired:=False, Order:=1)>
       5:      Public ReturnID As System.Nullable(Of System.Guid)
       6:      <DataMember(IsRequired:=True, Order:=2)>
       7:      Public FromCountry As String
       8:      <DataMember(IsRequired:=True, Order:=3)>
       9:      Public FromCity As String
      10:      <DataMember(IsRequired:=True, Order:=4)>
      11:      Public FromAirport As String
      12:      <DataMember(IsRequired:=True, Order:=5)>
      13:      Public ToCountry As String
      14:      <DataMember(IsRequired:=True, Order:=6)>
      15:      Public ToCity As String
      16:      <DataMember(IsRequired:=True, Order:=7)>
      17:      Public ToAirport As String
      18:      <DataMember(IsRequired:=True, Order:=8)>
      19:      Public FromDate As String
      20:      <DataMember(IsRequired:=True, Order:=9)>
      21:      Public FromTime As String
      22:      <DataMember(IsRequired:=True, Order:=10)>
      23:      Public FlyTime As String
      24:      <DataMember(IsRequired:=True, Order:=11)>
      25:      Public AviaCompanyCode As String
      26:      <DataMember(IsRequired:=True, Order:=12)>
      27:      Public FlyNumber As String
      28:      <DataMember(IsRequired:=True, Order:=13)>
      29:      Public FlyClass As String
      30:      <DataMember(IsRequired:=True, Order:=14)>
      31:      Public Price As String
      32:      <DataMember(IsRequired:=True, Order:=15)>
      33:      Public HowMany As String
      34:      <DataMember(IsRequired:=True, Order:=16)>
      35:      Public AirTransfer As String
      36:      <DataMember(IsRequired:=False, Order:=17)>
      37:      Public AirTransferComment As String
      38:      <DataMember(IsRequired:=True, Order:=18)>
      39:      Public Suplier As String
      40:   
      41:  End Class

  • AddReturnPrice - это сервис для загрузки таблицы цен к билету. Таблица цен описана структурой UploadReturnPrice. Здесь в поле Day0 вы указываете цену билета, а в поля Day1-Day31 - цену обратного билета. Вы также задаете период, когда действуют эти цены и номера рейсов. Supplier - это ваш логин. Метод возвраает ID записи о ценах на билеты.
  • Обратите внимание на поле Price. Здесь может быть указано просто число в десятичном формате (подобно 99999.99) - тогда это число рассматрвиается как цена в USD, либо числу предшествует "E" (E99999.99)- тогда это число рассматривается как цена в Евро.
  • Аналогично вид валюты в ценах на билеты определяется по наличию или отсутствию символа "E" в Day0 - простой цене прямого билета без обратного.


       1:  <DataContract()>
       2:  Public Class UploadReturnPrice
       3:   
       4:      <DataMember(IsRequired:=True, Order:=1)>
       5:      Public DateFrom As String
       6:      <DataMember(IsRequired:=True, Order:=2)>
       7:      Public DateTo As String
       8:      <DataMember(IsRequired:=True, Order:=3)>
       9:      Public FlyNumber As String
      10:      <DataMember(IsRequired:=True, Order:=4)>
      11:      Public Suplier As String
      12:      <DataMember(IsRequired:=True, Order:=5)>
      13:      Public Day0 As String
      14:      <DataMember(IsRequired:=True, Order:=6)>
      15:      Public Day1 As Decimal
    ...
      72:      <DataMember(IsRequired:=True, Order:=35)>
      73:      Public Day30 As Decimal
      74:      <DataMember(IsRequired:=True, Order:=36)>
      75:      Public Day31 As Decimal
      76:      <DataMember(IsRequired:=True, Order:=37)>
      77:      Public DayN As Decimal
      78:   
      79:  End Class

  • DelTicket - метод принимает в качестве параметра GUID билета и ваш логин и позволяет удалить из нашей базы один из ранее загруженных вами билетов.
  • DelReturnPrice - метод принимает GUID записи в таблице цен (ранее загруженную вами) и позволяет удалить запись о ценах.
  • GetMyTicketList - метод позволяет получить вам весь список билетов, которые удачно загрузилась нам в базу. Уточнить полную информацию о каждом вашем билете билете вы можете сервисом TicketList (методами GetOneTicketInfo или GetOneTicketDilerInfo - передавая каждый полученный ID билета этим методам).
  • GetMyTReturnPriceList - метод позволяет вам получить весь список записей о ценах, которые успешно загрузились нам в базу. Отдельно сервиса по вычитыванию из базы загруженных таблиц цен не предусмотрено - корректность данных таблицы цен проводите запрашивая цену на билеты методами GetOneTicketInfo или GetOneTicketDilerInfo.
  • SetSpecialTicket - сервис принимает GUID прямого и GUID обратного билета (ранее загруженного вами) и маркировать их как спецпредложение. Спецпредложения обычно показываются отдельно от общего массива билетов и предназначены для привлечения пользователей своей явной экономической выгодностью для покупателя. Вторая особенность этого сервиса - что вы можете объединить два билета в пакет - прямой/обратный - не в момент загрузки, а позднее, при пометке их как спецпредложение.



Описание сервиса http://service.flyseason.ru/Zakaz.svc?wsdl

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

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

Все сервисы работы с заказами работают с аутентификацией и принимают в качестве одного из парамеров структуру AU, которая описана выше. В структуре AU вы указываете флаг CryptoMode - работаете ли вы с открытым паролем/цифровойподписью или с зашифрованным.

Четыре метода требуют наличия цифровой подписи (в виде MD5-хеша) в поле Password структуры AU - AddTourist, AddZakaz, AddTouristToZakaz, DelZakaz.

Сервис состоит из следующих методов:

  • AddTourist - этот метод позволяет добавить туриста к заказу. ОБратите внимание на обязательные минимальные реквизиты туриста. Возвращает ID туриста.

  •    1:  <DataContract()>
       2:  Public Class OneTouristInfo
       3:   
       4:      'для создания туриста обязательно минимум два поля - Tel, Email
       5:      'В обязательное поле Tel можно писать телефон + имя
       6:      'Поле Skidka содержит "-"
       7:      'Поле Sex - Mr/Mrs/Inf - муж/жен/ребенок
       8:   
       9:      <DataMember(IsRequired:=True, Order:=1)>
      10:      Public Property Tel As String
      11:   
      12:      <DataMember(IsRequired:=True, Order:=2)>
      13:      Public Property Email As String
      14:   
      15:      <DataMember(IsRequired:=False, Order:=3)>
      16:      Public Property Skidka As String
      17:   
      18:      <DataMember(IsRequired:=False, Order:=4)>
      19:      Public Property Fam As String
      20:   
      21:      <DataMember(IsRequired:=False, Order:=5)>
      22:      Public Property Name As String
      23:   
      24:      <DataMember(IsRequired:=False, Order:=6)>
      25:      Public Property Sex As String
      26:   
      27:      <DataMember(IsRequired:=False, Order:=7)>
      28:      Public Property BirthDate As String
      29:   
      30:      <DataMember(IsRequired:=False, Order:=8)>
      31:      Public Property ZagPasspSerial As String
      32:   
      33:      <DataMember(IsRequired:=False, Order:=9)>
      34:      Public Property ZagPasspPnum As String
      35:   
      36:      <DataMember(IsRequired:=False, Order:=10)>
      37:      Public Property ZagPasspPdate As String
      38:   
      39:  End Class

  • AddZakaz - этим методом вы можете создать заказ. При создании заказа обязатательным параметром вы указываете только DirectTicketID (билет туда) и необязательно указываете ReturnTicketID (билет обратно). Метод возвращает ID созданного вами заказа.

  •    1:  <DataContract()>
       2:  Public Class OneZakazInfo
       3:   
       4:      <DataMember(IsRequired:=True, Order:=1)>
       5:      Public Property DirectTicketID As Guid
       6:   
       7:      <DataMember(IsRequired:=False, Order:=2)>
       8:      Public Property ReturnTicketID As Guid
       9:   
      10:  End Class

  • AddTouristToZakaz - После того, как вы загрузили туристов и создали заказ - вы этим методом должны добавить туристов в заказ (заполняя нижеследующую структуру TourustZakaz).

  •    1:  <DataContract()>
       2:  Public Class TourustZakaz
       3:   
       4:      <DataMember(IsRequired:=True, Order:=1)>
       5:      Public Property TouristGUID As Guid
       6:   
       7:      <DataMember(IsRequired:=True, Order:=2)>
       8:      Public Property ZakazGUID As Guid
       9:   
      10:  End Class

  • GetZakazState - этот метод предназначен для ослеживания состояния заказа. Передав этому методу ID-заказа вы получите полную инфрмацию о процессе обработки заказа Менеджерами компании. Значения флагов IsComplete, Status, ConfirmStatus, DocumentStatus присваивают Менеджеры компании. Их значения вам будут понятны после работы с web-админкой.

  •    1:  <DataContract()>
       2:  Public Class OneZakazState
       3:   
       4:      <DataMember(Order:=1)>
       5:      Public Property i As Integer
       6:      <DataMember(Order:=2)>
       7:      Public Property id As Guid
       8:      <DataMember(Order:=3)>
       9:      Public Property CrDate As DateTime
      10:      <DataMember(Order:=4)>
      11:      Public Property FromTicket As Guid
      12:      <DataMember(Order:=5)>
      13:      Public Property ToTicket As Guid
      14:      <DataMember(Order:=6)>
      15:      Public Property IsComplete As Integer
      16:      <DataMember(Order:=7)>
      17:      Public Property Status As Integer
      18:      <DataMember(Order:=8)>
      19:      Public Property ConfirmStatus As Integer
      20:      <DataMember(Order:=9)>
      21:      Public Property DocumentStatus As Integer
      22:   
      23:  End Class

  • DelZakaz - если ваш клиент отменил заказ - с помощью этого метода вы можете полностью удалить заказ из базы.
  • GetMyTouristList - этим методом вы получите список ID туристов, загруженных вами. Обзор их персональных данных через сервисы FlySeason не предусмотрен. Однако персональные данные всех загруженных вами туристов доступны в web-админке туристического агентства.
  • GetMyZakazList - этим методом вы можете получить список всех созданных вами заказаов. В дальнейшем ID каждого заказа передавайте методу GetZakazState и уточняйте состояние каждого заказа.
  • GetTouristInZakaz - этим методом вы можете обозревать перечень туристов в каждом заказе, который вы ранее добавили методом AddTouristToZakaz.

Тест цифровой подписи.

В разделе "Тестирование цифровой подписи" приведено описание метода GetTicketMD5 сервиса http://service.flyseason.ru/Upload.svc?wsdl - которым вы можете протестировать свой механизм создания цифровой подписи.

В этом разделе размещен тест, которым вы можете убедится в идентичности цифровой подписи MD5, создаваемой на любой платформе. Как вы можете видеть ниже - MD5-хеш расчитывается совершенно одинаково и на сервере (работающем на платформе NET) и на клиентском софте (реализованном на Flex).

При подключении с любой своей рабочей платформы (Perl, PHP, Python, Ruby, jQuery, JAVA и так далее) - вы должны добится чтобы ваш собственный софт правильно создавал цифровые подписи - и на любых данных цифровая подпись была такой же как в этом тесте. Иначе многие функции Web-сервисов будут вам недоступны. Все операции записи в базу FlySeason (например занесение в базу записи о заказанном билете) требуют достоверного MD5-хеша. Иначе любой продвинутый школьник запишет сеанс общения с сервисами и вопроизведет его с другими данными - например загрузит в базу вместо билетов какой-то мусор, который будет показываться всем вместо нормальных авиабилетов, или например проставит заказы на все существующие в базе билеты.

Комплексный тест криптографии сервисов FlySeason.

Ниже размещен комплексный тест, который позволяет вам без XmlSpy или SoapUI прямо с этой странички проверить полученный вами у Менеджеров компании Ключ/IV алгоритма Rjndael и пароль.

В форму теста вам надо ввести полученные от менеджеров данные и ввести ID-билета (который показывается на сайте в строке URL после нажатия кнопки ЗАКАЗАТЬ). Если информация о билете выдается, то вы получили от Менеджеров все, что вам необходимо для разработки вашего софта.

Обратите внимание, что это является также тестом совместимости наших сервисов с другими платформами, в частности этот тест написан на Flex (а наши SOAP/WSDL-сервисы реализованы на NET).

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



Исходные тексты обоих тестов на Flex являются OpenSource и опубликованы здесь. Вы можете воспользоватся этим кодом как образцом для построения своих клиентов.

Развитие сервисов FlySeason в следующем релизе.

Первое направление расширения сервисов - совершенствование механизма защиты сервисов от взлома. В текущем релизе не было завершено тестирование ECHD-криптографии, что снижает защищенность сервисов.

Второе направление расширения web-сервисов компании FlySeason - предоставление новых сервисов. В часности в следующем релизе будут сервисы чтения новостей сайта (которые вы можете добавлять в web-админке) и сервисы календарного анализа наличия чартерных авиабилетов.