Введение.
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
Этот сервис предназначен для получения курса валюты (USD) по которому
работает FlySeason. Все цены во всех сервисах вы получите либо в
долларах США либо в Евро (в зависимости от MoneyType - который при
загрузке прайсов и цен определяется по наличию символа "E" в ключевом
поле, а при выводе билетов и цен по полю MoneyType) и чтобы пересчитать
их в рубли - вам надо умножить цену на курс валюты (который вы как и
получите этим сервисом).
Сервис содержит единственный метод без параметров и не требует аутентификации:
В связи с такой исключительной простотой этого сервиса мы бы рекомендовали вам начать подключение именно с этого сервиса.
Этот сервис предназначен для получения курса валюты (EUR) по которому
работает FlySeason. Все цены во всех сервисах вы получите либо в
долларах США либо в Евро (в зависимости от MoneyType - который при
загрузке прайсов и цен определяется по наличию символа "E" в ключевом
поле, а при выводе билетов и цен по полю MoneyType) и чтобы пересчитать
их в рубли - вам надо умножить цену на курс валюты (который вы как и
получите этим сервисом).
Сервис содержит единственный метод без параметров и не требует аутентификации:
В связи с такой исключительной простотой этого сервиса мы бы рекомендовали вам начать подключение именно с этого сервиса.
Сервис CityCountry является открытым сервисом, не требует
аутентификации. Он сообщает по каким странам и городам у нас есть билеты
на чартерные рейсы. Основное назначение этого сервиса - дать вам
правильные наименования стран и городов в нашей базе. Все запросы к
другим сервисам вы будете строить с этими наименованиями.
- GetCountry - не требует параметров. Выдает список стран.
- GetCity - принимает в качестве параметра страну и выдает список городов.
Сервис 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
Этот сервис предназначен для загрузки ваших чартерных билетов в нашу базу. Сервис состоит из следующих методов:
Все сервисы загрузки билетов в базу (кроме 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
обратного билета (ранее загруженного вами) и маркировать их как
спецпредложение. Спецпредложения обычно показываются отдельно от общего
массива билетов и предназначены для привлечения пользователей своей
явной экономической выгодностью для покупателя. Вторая особенность этого
сервиса - что вы можете объединить два билета в пакет - прямой/обратный
- не в момент загрузки, а позднее, при пометке их как спецпредложение.
Этот сервис предназначен для записи в нашу базу заказов на чартерные
авиабилеты. В дальнейшем вы можете через админку (или прямо через этот
сервис) отслеживать состояние заказа и его оплату.
Обратите внимание, что со своими заказами вы можете работать не
только с помощью данных сервисов, но и непосредственно из админки
туристического агентства.
Все сервисы работы с заказами работают с аутентификацией и принимают в
качестве одного из парамеров структуру 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-админке) и сервисы календарного анализа наличия чартерных
авиабилетов.