środa, 27 stycznia 2016

Meandry PoRD - Ustępowanie pierwszeństwa z uwagi na obecność tylko linii warunkowego zatrzymania złożonej z trójkątów

Teza:

jeśli na skrzyżowaniu nie ma oznakowania pionowego w postaci znaku A-7 (znak Ustąp pierwszeństwa), natomiast istnieje oznakowanie poziome w postaci linii P-13 (linia warunkowego zatrzymania złożona z trójkątów), obecność tylko i wyłącznie linii P-13 nie decyduje o pierwszeństwie.

Podstawa prawna:
Rozporządzenie w sprawie znaków i sygnałów drogowych
§ 89.2. Znak P-13 "linia warunkowego zatrzymania złożona z trójkątów" wskazuje miejsce zatrzymania pojazdu w celu ustąpienia pierwszeństwa wynikającego ze znaku pionowego A-7.

sobota, 2 stycznia 2016

Odwrotna inżynieria klienta web JD Edwards

Miałem ostatnio potrzebę "podszycia" się aplikacją pod klienta web systemu JD Edwards EnterpriseOne (w zasadzie obecnie jest to serwer aplikacyjny Oracle, tu w wersji 10g). Rzecz mało popularna, ale czasami przydatna (interakcja z aplikacją web w sposób inny, niż ręczny) do zautomatyzowania rzeczy, których nie da się zautomatyzować w inny sposób z powodu, hmmm... "odgórnych ograniczeń obiektywnych".
Brak publicznie dostępnej dokumentacji działania klienta web zmusza do analizy działania aplikacji i prób zrozumienia, co też tam tak naprawdę się dzieje. Wszystko to po to, żeby umieć naśladować w poprawny sposób tą komunikację. Nie przypuszczam, aby zebrane w tym poście informacje były użyteczne dla tłumów programistów ;-), nie mniej cele dokumentacyjne mogą być istotne dla ewentualnego późniejszego powrotu do tematu.
Zaznaczę, że poniższe wyjaśnienia opcji są rezultatem tego, co mi się wydaje, a nie rzeczywistym potwierdzeniem tego, co tak naprawdę się dzieje, nie mniej empiria zdaje się potwierdzać te wnioski.

Nieodzownym narzędziem, które pomogło w analizie, były narzędzia deweloperskie wbudowane w przeglądarkę internetową (tu Internet Explorer jako, że inne przeglądarki nie są kompatybilne z tym klientem web). Samo narzędzie tego typu pozwala śledzić m. in. zapytania wysyłane przez przeglądarkę i odpowiedzi od systemu.


Komunikacja wygląda, mniej więcej, następująco.

Klient wykorzystuje Cookies do zapisania klucza sesji oraz stanu aplikacji. Są to klucze:
JSESSIONID oraz e1AppState. Konieczne jest więc pamiętanie tych informacji pomiędzy zapytaniami wysyłanymi do serwera aplikacyjnego. W dobrym kliencie web dzieje się to automatycznie (np. klient WebClient w środowisku .Net nie posiada takiej możliwości, konieczne jest skorzystanie z klasy CookieAwareWebClient).

Logowanie do aplikacji

Przeglądarka wysyła polecenie POST, w treści żądania zobaczymy informacje wprowadzone w oknie logowania:
User=nazwa-użytkownika
Password=hasło
Environment=środowisko
Role=
RENDER_MAFLET=E1Menu
po czym, w treści odpowiedzi ładowane jest źródło HTML klienta, poleceniami GET dociągane są pozostałe elementy strony - słowem nic, nad czym warto byłoby się rozwodzić.

Otwarcie aplikacji systemu

Następnie, aby dostać się do odpowiedniej opcji menu, operator zazwyczaj rozwija sobie drzewo opcji, które skutkuje za każdym razem wysłaniem polecenia POST. Przy "klikaniu" automatycznym interesuje mnie w zasadzie otwarcie od razu docelowej formatki, więc przechodzimy do POSTa, który mnie interesuje. POSTem tym żądamy otwarcia konkretnej aplikacji systemu.

Nagłówek żądania:

POST /jde/RunApp.mafService?
e1.state=maximized
e1.mode=view
e1.namespace=
e1.service=RunApp
RENDER_MAFLET=E1Menu
RID=xxxxxxxxxxxxxxx

Treść tego żądania:

timestamp=1447235621788 - liczba milisekund od 1970-01-01 tak, jak zwraca to funkcja JavaScript getTime(). Wartość ta jest (prawdopodobnie) wykorzystywana, wspólnie z kolejną wartością w jednym z późniejszych zapytań GET, do szacowania czasu odpowiedzi aplikacji
launchAction=launchForm
promptPO=0
appID=xxxxxx - identyfikator aplikacji
appType=APP
formID=xxxxxxx - identyfikator formularza, który ma zostać wyświetlony
version=xxxxxxx
mode=1
taskName=xxxxxxxxxxxxxxxxxxxxxx - nazwa uruchamianej aplikacji
jdemafjasLinkTarget=E1MENUMAIN_XXXXXXXXXXXXXXXXXXX - identyfikator sesji, wartość ta jest również widoczna w polu Cookie
namespace=
RID=xxxxxxxxxxxxxxxxxx -
identyfikator sesji, który jest nadawany przy logowaniu. Razem z jdemafjasLinkTarget powinien być odczytany bezpośrednio po logowaniu z podanego przez serwer kodu HTML.


Otwarcie właściwego formularza

W odpowiedzi żądania otrzymałem stronę z formularzem, na którym "klikam" sobie przycisk Add w celu otwarcia właściwego formularza, w którym będą wprowadzane dane.
Po kliknięciu przycisku wysyłany jest pierwszy POST:

Nagłówek żądania:

POST /jde/E1VirtualClient.mafService?
e1.state=maximized
e1.mode=view
e1.namespace=
e1.service=E1VirtualClient
RENDER_MAFLET=E1Menu



Treść tego żądania:

jdemafjasLinkTarget=E1MENUMAIN_XXXXXXXXXXXXXXXXXXX - ten sam identyfikator sesji, jak powyżej
formOID=xxxxxxx - identyfikator formularza
guiChangeOnly=1
stackId=1 - identyfikator stosu - umożliwia uruchamianie wielu aplikacji w tym samym środowisku, podczas działania całej aplikacji, identyfikator jest zawsze ten sam - u mnie 1, bo jest to jedyna aplikacja, którą uruchamiam
stateId=1 - identyfikator stanu - podczas każdej transakcji wartość jest zwiększana o 1
RID=xxxxxxxxxxxxxx - identyfikator sesji
portalContainerType=Non-WebCenter - informacja, że aplikacja nie jest typu Oracle WebCenter
eventCount=2 - liczba identyfikatorów 0_, 1_ itd.
posX=0 - położenie formularza w poziomie w stosunku do okna przeglądarki, co pozwala na wyświetlenie go w tym samym miejscu po odświeżeniu formularza. Najczęściej 0, bo zazwyczaj cały formularz mieści się w szerokości okna
posY=0 - jak wyżej tyle, że w pionie
0_cmd=ctrlExit - informacja, że pole o identyfikatorze 0_202 jest opuszczane
0_ctrlID=0_202 - identyfikator pola
1_cmd=0_65 - informacja, że został "kliknięty" przycisk "Add"

W treści odpowiedzi tego zapytania przekazywany jest docelowy link oraz kawałek kodu HTML powodującego odświeżenie strony.

Przekazywany link wygląda następująco:
/jde/E1Client.mafService?
jdemafjasLauncher=PSFT_TE_V3_SW
e1.state=maximized
e1.mode=view
e1.namespace=
e1.service=E1Client
jdemafjascacheUID=xxxx
jdemafjasUID=x
jdemafjasComponent=JDE_HTMLCLIENT
RENDER_MAFLET=E1Menu
Wartości jdemafjascacheUID i jdemafjasUID są takie same przez całą sesję i również służą jej identyfikacji.

Wysyłane jest kolejne zapytanie POST z użyciem przekazanego powyżej linku. W treści żądania przekazywane są:
portletstateparameter=max
jdemafjasLinkTarget=E1MENUMAIN_XXXXXXXXXXXXXXXXXXX - identyfikator jest cały czas taki sam, jak powyżej
RID=xxxxxxxxxxxxxx - cały czas ten sam Resource ID
portalContainerType=Non-WebCenter
W odpowiedzi otrzymujemy już docelową stronę z formularzem.

Wypełnianie pól formularza

Po wypełnieniu i opuszczeniu każdego z pól formularza, wysyłane jest żądanie POST z zawartością tego pola. To umożliwia na bieżąco weryfikowanie poprawności wprowadzonej do pola wartości.

Nagłówek żądania:

POST /jde/E1VirtualClient.mafService?
e1.state=maximized
e1.mode=view
e1.namespace=
e1.service=E1VirtualClient
RENDER_MAFLET=E1Menu


Treść żądania:

jdemafjasLinkTarget=E1MENUMAIN_XXXXXXXXXXXXXXXXXXX - ponownie ten sam identyfikator
formOID=xxxxxxx
guiChangeOnly=1
stackId=1
RID=xxxxxxxxxxxxxx
portalContainerType=Non-WebCenter
stateId=2
eventCount=3 - trzy grupy (0_, 1_, 2_)
posX=0
posY=0
0_cmd=ctrlExit - informacja, że pole jest opuszczane
0_ctrlID=0_28 - identyfikator powyższego pola
1_cmd=ctrlExit - kolejne pole, które zostało opuszczone
1_ctrlID=0_859 - identyfikator powyższego pola
1_ctrlVal=www%40www.pl - wartość pola
2_cmd=activeElement - informacja, że pole staje się aktywne
2_ctrlID=0_28 - identyfikator tego pola

Klient web zbiera wszystkie zdarzenia, które zaszły na formularzu, w tym np. przejście zaznaczenia do kolejnych pól, zmiany wartości pól, itd. Rodzajów różnych kontrolek na formularzu może być wiele. Wszystkie zdarzenia są kolejkowane i wysyłane w pakiecie POST jako kolejne komendy 0_, 1_, 2_, itd.
W odpowiedzi na żądanie POST możemy nie otrzymać nic, a możemy otrzymać np. kod HTML z informacją, że wartość pola nie jest poprawna.
Wartość eventCount odpowiada rzeczywistej liczbie pól (komend), aby mogły być one prawidłowo obsłużone po stronie serwera aplikacyjnego.
Warto zauważyć, że identyfikator stateId ulega zwiększeniu o 1 przy wykonywaniu transakcji, to jest np. otwarcie odpowiedniego formularza, zamknięcie formularza, ale nie przy samym wysyłaniu wartości kolejnych pól formularza do serwera. Prawdopodobnie najlepiej jest empirycznie prześledzić, kiedy wartość ulega zwiększeniu. Nieprawidłowa obsługa stateId może prowadzić do odrzucenia wprowadzonych danych czego przyczynę, nawiasem mówiąc, ciężko będzie ustalić.

Na razie tyle :-)