[KINTER] Działający produkt!

Mogę śmiało powiedzieć że mój projekt na Daj Się Poznać 2017 został ukończony w pewnym sensie. Dlaczego w pewnym? Ponieważ aktualnie program robi to co miał robić, jednak jest skonfigurowany jedynie pod moje środowisko i osoby które chciałyby korzystać z mojego narzędzia na pewno musiałby by dokonać poprawek w kodzie.

Dlatego też w przyszłości planuję tak rozbudować KINTERA aby każdy kto posiada oprogramowanie firmy StreamSoft oraz PrestShop mógł bez większego problemu skonfigurować mój program bez potrzeby grzebania w kodzie, a jedynie w pliku konfiguracyjnym.

Użytkownik będzie musiał podać takie rzeczy jak API do sklepu, ID niektórych pól ze StreamSoftu (cechy produktów, opisów, itp.) oraz ID dokumentów które mają się tworzyć przy przenoszeniu zamówień ze sklepu, również ścieżkę do folderu ze zdjęciami. Oczywiście będą również wymagane dane do połączenia się z bazą danych firebird z której korzysta StreamSoft.

Bardzo się cieszę że wytrwałem te kilka tygodni i się nie poddałem. Nie ukrywam że pod koniec było mi ciężko pisać posty i kodować ponieważ mam teraz wiele spraw na głowie, w tym egzaminy na studiach.

Chciałbym też poprawić jakość tego kodu abym mógł go pokazywać przy okazji składania CV do innych firm, jako dowód na to że umiem w pewnym stopniu programować. Mam nadzieję że w niedalekiej przyszłości znajdę pracę jako programista 🙂

Zapraszam do sprawdzenia mojego kodu znajdującego się na github.com/szyn33k i komentowania.

[KINTER] Kilka przemyśleń

Od kilku dni zastanawiam się czy nie muszę dodać do swojego projektu bazy danych. Mam potrzebę zapisania np. daty ostatniego sprawdzenia nowych zamówień albo prostych ustawień konfiguracyjnych. Dodatkowo umieścił bym w niej powiązania kategorii z ERP z kategoriami w sklepie (aktualnie zmodyfikowałem pliki sklepu aby mógł umieścić w nim kategorie z własnymi numerami ID, więc numer id kategorii z ERP = numerowi kategorii w sklepie) i to samo bym zrobił z atrybutami cech.

Zastanawiam się również jak powinna przebiegać konfiguracja programu po stronie użytkownika. Chciałbym ograniczyć grzebanie w kodzie aby ustawić go pod swój system jednak nie chcę też tworzyć aplikacji okienkowej bo w przyszłości chcę wyemigrować z kodem na dot NET, aby można było uruchomić program zarówno na Linuxie jak i na Windowsie.

I tutaj chyba pojawia się rozwiązanie, mianowicie zamiast konsolowej aplikacji oraz okienkowej stworzyłbym program jako stronę WWW, w ASP NET. Niestety aktualnie w ogóle nie znam tej technologii więc trochę nauki przede mną.

Myślę że to rozwiązanie byłoby najlepszym wyjściem.

Aktualny kod znajduje się już na GITHUBIE.

[KINTER] Ciągłe rozwijanie funkcji dodającej zamówienie

Szybki news.

W swoim projekcie ciągle rozwijam funkcję odpowiedzialną za dodanie zamówienia do Prestiża. Co chwilę wyskakują rzeczy których pierwotnie nie było w planie. Została dodana również funkcja sprawdzająca czy dane zamówienie istnieje już w prestiżu czy nie. W wolnych chwilach zajmuję się refaktoryzacją kodu któremu według mnie jeszcze sporo do ideału 😉

Aktualny commit już na githubie.

[WINDOWS] Email z informacją o połączeniu się z pulpitem zdalnym

Ostatnio musiałem wykombinować coś aby system windows wysyłał mi emaile z informacją kto i kiedy łączy się do serwera poprzez pulpit zdalny.

Najprościej można to osiągnąć poprzez wysyłanie maila ze skryptu batch który z kolei jest podpięty do odpowiedniego zdarzenia. Wybrałem zdarzenia z miejsca: Microsoft-Windows-TerminalServices-LocalSessionManager o ID 21 oraz 25.

Skrypt który wysyła maila wygląda tak:

@ Echo Off
wevtutil qe Microsoft-Windows-TerminalServices-LocalSessionManager/Operational /rd:false /c:1 /f:text >> c:\%username%logon.txt
sendEmail.exe -f from@email.com -t to@email.com -s smtp.host.com -xu user -xp pass -u "TEMAT" -o message-file=c:\%username%logon.txt
del c:\%username%logon.txt

W powyższym kodzie używam narzędzia wevtutil które pobiera mi szczegóły zdarzenia, takie jak kto się łączy, skąd, o której godzinie. Następnie zapisuje wynik do pliku który następnie jest wysyłany w mailu dzięki narzędziu sendEmail dostępnym tutaj.

Jak widzimy całość nie jest w ogóle skomplikowana a działa poprawnie.

[KINTER] Czy ta funkcja jest potrzebna?

W swoim projekcie mam metodę która sprawdza czy na sklepie pojawiły się nowe zamówienia która wygląda mniej więcej tak:

  • Połącz się ze sklepem
  • Pobierz zamówienia których data dodania jest równa lub większa dacie ostatniego sprawdzenia
  • Sprawdź ile jest takich zamówień
  • Jeżeli ilosc > 0 return true
  • W przeciwnym wypadku return false

I jeżeli ta metoda zwróci mi true to w dalszej części pobieram nowe zamówienia bardzo podobną metodą z tym że zwracam listę zamówień zamiast wartości boolean.

Zastanawiam się czy nie uprościć tego do postaci mniej więcej takiej:

  • Pobierz nowe zamówienia
  • Jeżeli ilość zamówień >0 to (wykonaj pewny kod)

Po co powielać metody które prawie dokładnie robią to samo? Z tym że jedna zwraca mi true/false w zależności od ilości nowych zamówień a druga listę tych zamówień, a to samo mogę uzyskać operując na tejże zwróconej liście.

Co mnie powstrzymuje przed tymi zmianami?

Powinienem na pewno zmodyfikować funkcję która sprawdza czy są nowe zamówienia. Nie mogę sprawdzać zamówień tylko po dacie dodania, powinienem zaimplementować kod który sprawdzi dodatkowo czy zamówienie o takim numerze znajduje się już w Prestiżu. Może zdarzyć się taka sytuacja w której będziemy synchronizować zamówienia i nagle połączenie do bazy się zerwie i program zdąży dodać na przykład tylko 2 z 4 pozycji w zamówieniu. Wtedy ewentualne rozbieżności trzeba poprawić.

W przeciągu najbliższych dni postaram się wymyślić jakieś rozwiązanie ale wiem że to nie będzie łatwe zadanie.

O postępach poinformuję w osobnym wpisie.

Lychee – bardzo rozbudowana galeria zdjęć

Szukając oprogramowania w którym mógłbym w łatwy i przyjemny sposób tworzy i zarządzać albumami i zdjęciami natrafiłem na oprogramowanie o nazwie Lychee ( dostępny na GitHubie o tutaj ).

Wersję demo można zobaczyć pod tym adresem.

Oprogramowanie spełnia większość moich wymagań m .in.: bez problemu można tworzyć albumy, wgrywać do nich obrazy o wielkim rozmiarze, udostępniać te albumy, zabezpieczać je hasłem. Albumy można opisywać w dowolny sposób. Tak samo zdjęcia. Dodatkowo przy wgrywaniu zdjęć skrypt pobiera dane Exif które są wyświetlane w szczegółach zdjęcia.

Jest również super działająca wyszukiwarka który wyszukuje podaną frazę w tytułach i opisach albumów oraz w opisach zdjęć. Jest również integracja z Dropboxem.

Niestety brakuje mi tutaj dwóch albo nawet trzech funkcjonalności. Skrypt nie wspiera tworzenia albumu w albumie, a szkoda bo bardzo ułatwiło by mi to życie. Również brakuje wsparcia wielu użytkowników z uprawnieniami. Na razie jest tylko jedno konto administratora. Trudne jest również przetłumaczenie tego skryptu na język polski. Cały widok jest zakodowany w dziesiątkach plikach .JS

Mam nadzieję że w najbliższym czasie przynajmniej jedna z tych rzeczy zostanie zaimplementowana. Z całego serca polecam wam ten skrypt bo jest na prawdę wysokiej jakości.

[KINTER] Dodanie nowej funkcjonalności

Od początku tego tygodnia walczyłem z kodem który będzie pobierał ze sklepu nowe zamówienia oraz dodawał je do programu ERP.

Na początku musiałem określić jak zdefiniować “nowe” zamówienia. Czy sprawdzenie po dacie ich dodania wystarczy czy jednak muszę jeszcze sprawdzić czy nie istnieje już identyczne zamówienie w prestiżu.

Postanowiłem na razie oprzeć się wyłącznie na dacie, chociaż najpewniej będzie to zmienione w niedalekiej przyszłości, ponieważ już teraz rodzi się pytanie gdzie przechowywać datę ostatniego sprawdzenia zamówień. Raczej zdecyduje się na stworzenie bazy danych i trzymanie daty w niej.

Dodatkowo przyszło mi na myśl że w tej bazie mógłbym również trzymać ID kategorii z ERP i ze sklepu dzięki czemu nie musiałbym modyfikować plików od sklepowego API oraz od nieoficjalnego klienta do tego API – RestSharpa.

Ale to jeszcze muszę przemyśleć.

Skoro pobieram już nowe zamówienia to muszę je dodać do programu. W bazie danych ERP są gotowe procedury jednak o wiele wygodniej jest mi dodać zamówienie prosto do bazy za pomocą INSERTa zamiast za pomocą tej procedury.

Swoją drogą, w DB istnieje aż 8 procedur do dodania zamówienia:

Po dodaniu zamówienia do bazy muszę jeszcze dodać osobno pozycję do tego zamówienia. Też niby jest gotowa procedura (tym razem 1 a nie aż 8) ale i tym razem wolę wykonać prostego INSERTa.

Może w wolnej chwili siądę i przepiszę swój kod tak aby używał procedury ale najpierw będę musiał poznać te procedury 😉

Tak więc zostało mi jeszcze trochę poprawek w kodzie bo czasami pozycje nie przenoszą się poprawnie ale wszystko zmierza w dobrym kierunku.

Na dniach na GITHUBIE kod zostanie zaktualizowany.

Jakie subreddity warto przeglądać?

Na pewno większość osób kojarzy pierwowzór wykopu – Reddit. Znajdziemy tam na prawdę sporo interesujących treści a w tym krótkim wpisie chciałbym wam pokazać kilka subredditów które osobiście często przeglądam.

/r/homelab/ – w tym dziale ludzie dzielą się swoimi własnymi domowymi serwerami a czasami nawet serwerowniami które mają w domu.
/r/sysadmin/ – porady, problemy, opinie administratorów sieciowych. Często również tzw. fuckupy 😉
/r/Cisco/ – wszystko o produktach Cisco.
/r/commandline/ – coś dla osób które bardziej niż GUI cenią wiersz poleceń.
/r/linuxadmin/ – dla osób które mają styczność z linuxem.
/r/netsec/ – dział o szeroko pojętym bezpieczeństwie w sieci
/r/networking/ – informacje o budowie sieci oraz konfiguracji sprzętu sieciowego

Oraz te bardziej znane oraz oczywiste:

/r/dotnet – nowinki ze świata .NETu
/r/csharp/ – informacje o C#
No i oczywiście jest też /r/ios/ oraz /r/Android/

Znacie jakieś fajne subreddity które warto śledzić? Napiszcie o tym w komentarzu!

 

Podsumowanie pierwszego miesiąca

Jutro minie dokładnie miesiąc od czasu kiedy postanowiłem zapisać się do konkursu “Daj się poznać” organizowanego przez Maćka Aniserowicza.

Mogę powiedzieć że zapału mi nie brakuje, wręcz przeciwnie. Za każdym razem jak widzę posty na blogów innych uczestników oznajmiających że rezygnują z udziału w DSP dostaję dodatkowego kopa i liczę na to że ja nie polegnę i wytrwam do końca 😉

Projekt ciągle jest rozwijany, mam kilka nowych pomysłów, które chciałbym wdrożyć jednak brakuje mi czasu. Aktualnie program przenosi do sklepu produkty, kategorie, opisy, zdjęcia, cechy i stany magazynowe, a od kilku dni czytam dokumentację Stream Softu do Prestiża aby zacząć pisać synchronizację w drugą stronę. Najwyższa pora napisać kod który będzie przenosił zamówienia oraz klientów ze sklepu.

Na dniach puszczę kolejnego commita i na githubie będzie aktualna wersja.

 

[C#] const vs readonly

W C# istnieją dwa sposoby definiowania stałych. Możemy użyć słowa kluczowego const lub readonly. Czy są jakieś różnice? Oczywiście.

Z definicji języka C# dowiadujemy się że const przechowuje wartość która jest przypisywana w czasie kompilacji. Natomiast wartość dla readonly może być obliczona dynamicznie ponieważ jest przetwarzana na poziomie runtime.
Dodatkowo stałej const nie możemy zapisać razem ze słówkiem static ponieważ const sama w sobie jest już statyczna. Do wartości można się odwołać poprzez ClassName.ConstantName.
readonly można zapisać jako stałą statyczną.

Ważną sprawą również jest to że jeżeli stałą const będziemy używali w innych bibliotekach, niż ta w której została zadeklarowana, to każda zmiana wartości const będzie wymagała rekompilacji bibliotek z niej korzystających.

Dlatego jeżeli mamy pewność że nasza stała nigdy nie ulegnie zmianie, możemy śmiało zapisać ja za pomocą const, jeżeli jednak wartość stałej może się kiedyś zmienić to bezpieczniej będzie ją zapisać jako readonly.

Sprawozdanie z postępu prac nad KINTEREM

Dzisiaj poszedł kolejny commit na githuba więc pora opisać co zostało w projekcie zmienione a co dodane.

Zdjęcia

Musiałem synchronizować zdjęcia towaru oraz porównywać je czy zostały zmienione czy nie. Do porównywania zastosowałem bibliotekę EyeOpen która działa całkiem przyzwoicie lecz aby porównać 1000 zdjęć potrzeba około 40 sekund. Zamierzam to w jakiś sposób zoptymalizować – na razie odkładam to na drugi plan.

Kategorie

Musiałem powalczyć ze synchronizacją kategorii ponieważ jak wspomniałem w poprzednich postach nieoficjalny klient API nie pozwalał na dodawanie własnego numeru ID, nie pozwalało również na to API po stronie sklepu. Wymagana była edycja niektórych plików z czego nie jestem zadowolony ponieważ chciałem unikać ingerencji w pliki sklepu. Również to jest jeszcze do poprawy.

Atrybuty

Rozpocząłem pisać kod który przeniesie mi atrybuty do sklepu czyli np. wymiar, waga, kolor itp. Temat nie jest trudny ale czasochłonny, a w tym tygodniu miałem wyjątkowo mało czasu na rozwijanie KINTERA.

 

Mam nadzieję w następnym tygodniu skończę niektóre rzeczy i przejdę do synchronizacji zamówień. W ogóle muszę się pochwalić że bardzo podoba mi się nazwa KINTER , nie dość że wymyśliłem ją na poczekaniu to brzmi całkiem nieźle, zawiera w sobie INTER które mówi nam o INTEGRACJI oraz jest krótkie i jest łatwe do wypowiedzenia.

Całkiem fajnie 😀

31 marca – Światowy Dzień Backupu!

Dziś 31 marca czyli Światowe Święto Backupu! Sam nie miałem pojęcia że jest taki dzień, ale skoro się dowiedziałem to chciałbym abyście przeczytali super artykuł Adama z ZaufanejTrzeciejStrony w którym mówi jak się zabezpiecza w sieci. Artykuł pod tym linkiem.

Po publikacji tego wpisu Adam otrzymał wiele maili z pytaniami i po jakimś czasie opublikował ciąg dalszy – zobacz co napisał.

Na pewno warto również pokazać wam dosyć obszerny tekst napisany na Sekuraku również na temat kopii zapasowych. Oto link.

Zawsze warto pamiętać że backupy są niezwykle istotne! Sam się o tym przekonałem nie jeden raz i wiem że posiadanie kopii potrafi zaoszczędzić wiele nerwów. Chyba nikt nie chce stracić swoich zdjęć lub -co gorsza- bazy danych naszego bloga!

Na koniec przytoczę cytat który przytacza się zawsze jeżeli mówi się o kopiach:

Ludzie dzielą się na tych, którzy robią kopie zapasowe i tych, którzy będą je robili – autor nieznany.

Więc, nie zwlekaj! Skonfiguruj kopię już dziś!

 

[NET Core] Kompilowanie projektu

Pisząc aplikację w NET Core zawsze uruchamiałem ją z poziomu konsoli wpisując dotnet run. Wszystko fajnie działało, ale przyszła pora kiedy musiałem przerzucić aplikację na serwer na którym miała być uruchomiona.

Nie chciałem tam instalować SDK NET Cora ale uruchamiać ją w harmonogramie zadań wskazując po prostu na plik .exe.

Był tylko jeden problem. Podczas kompilowania plik exe nie był tworzony, ponieważ sam NET Core w locie kompiluje naszą aplikację do kodu pośredniego, do pliku .dll, a następnie maszyna wirtualna przeprowadza kompilację do kodu maszynowego. Jest to tak zwana kompilacja just-in-time o której więcej można poczytać tutaj lub tutaj.

Oczywiście można bez problemu stworzyć plik exe i właśnie w tym poście chciałbym opisać jak można tego dokonać.

Po pierwsze musimy wyedytować plik NazwaProjektu.csproj i w sekcji <PropertyGroup> dodać podsekcję <RuntimeIdentifiers>. W niej umieszczamy tzw. RIDs czyli Runtime IDentifier. Każdy system ma swój RID.

Przykładowo:

  • win7-x64 – 64 bitowy Windows 7
  • win7-x86 – 32 bitowy Windows 7
  • win10-x64 – 64 bitowy Windows 10
  • ubuntu.14.04-x64 – 64 bitowy Ubuntu

Listę wszystkich RIDsów można znaleźć pod tym adresem.

Czyli nasz plik .csproj będzie wyglądał np tak:


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
      <OutputType>Exe</OutputType>
      <TargetFramework>netcoreapp1.1</TargetFramework>
      <RuntimeIdentifiers>win10-x64;osx.10.11-x64</RuntimeIdentifiers>
   </PropertyGroup>
   <ItemGroup>
      <PackageReference Include="FirebirdSql.Data.FirebirdClient" Version="5.8.0"/>
      <PackageReference Include="MimeKit" Version="1.12.0"/>
      <PackageReference Include="NETCore.MailKit" Version="1.0.1"/>
   </ItemGroup>
</Project>

Widzimy w linii nr nasz dodany kod.

Teraz wystarczy w konsoli wpisać polecenie dotnet publish -c Release -r win10-x64. Jeżeli podaliśmy więcej RIDsów to wykonujemy tę komendę z kolejnymi RIDsami. W moim powyższym przykładzie powinienem wykonać komendę dla win10-x64 oraz dla oxs.10.11-x64. Po wykonaniu komend w folderze .\bin\Release\netstandard1.6\ pojawią nam się odpowiednie pliki. W tym plik EXE który był mi potrzebny.

Cały proces bardzo dobrze jest opisany na oficjalnej stronie Microsoftu – link.

Echo <br/>

Szkolenie “Podstawy C# oraz .NET Core” – wrażenia

Ostatnio przeglądając snapchata (jednak to się do czegoś przydaje!) Piotra Gankiewicza dowiedziałem się że planuje szkolenie we Wrocławiu z podstaw C# oraz .NET Core.

Kurcze! Lepiej trafić nie mógł. Nie dość że właśnie w tym języku piszę program na DSP2017 to jeszcze miasto jest względnie blisko i mogłem bez problemu wziąć w tym wydarzeniu udział.

Rejestracja na meetupie rozpoczęła się o 9.00 i koło 9.45 już nie było miejsc 😉 Na szczęście zapisałem się w porę i w sobotę 25 marca pokonałem ~200km i znalazłem się we Wrocławiu.

Szkolenie odbyło się w biurze PGS Software – bardzo fajne miejsce i od razu było widać że z IT ma wiele wspólnego 😉

Na początku spotkania szybko omówione zostało czym jest C#, .NET Core oraz jaki porządek będzie miało szkolenie.

Przeszliśmy od totalnych podstaw (typy zmiennych, pętle) poprzez interfejsy, listy, klasy, dziedziczenie. Poruszone zostały także wyjątki, delegaty oraz typy generyczne. Szkoda że na LINQ nie starczyło już czasu. Grupa uczestników była całkiem w porządku, nikt nie zwalniał tempa dzięki czemu zrealizowaliśmy ponad 3/4 zaplanowanego materiału 🙂

W międzyczasie uczestnicy zostali poczęstowani pysznymi burgerami. Były też różne przekąski oraz napoje. No i obowiązkowo były też gumowe kaczuszki 😀 Zobaczymy czy szybciej będzie się debugowało 😉

Ogólnie jestem zadowolony ze szkolenia, jednakże dla osoby która miała jakąś styczność z programowaniem poziom mógł być trochę za niski ale w tytule było wyraźnie napisane “Podstawy” więc niczego innego spodziewać się nie powinniśmy.

Mam nadzieję że Piotr będzie prowadził jakieś szkolenie trochę bardziej zaawansowane. Na pewno z chęcią wezmę w tym udział!

Jeżeli kogoś nie było bo nie zdążył się zapisać albo zwyczajnie nie mógł się zjawić to polecam kurs Piotra w którym omawia dokładnie te same elementy.

Link do kursu on-line 

Czy bym komuś polecił szkolenia które prowadzi Piotr? Oczywiście! Spetzu to super pozytywna osoba która potrafi przekazać swoją wiedzę i z chęcią pomaga innym w rozwiązywaniu problemów.

Cieszę się że poznałem Piotra i każdemu polecam śledzić jego poczynania, szczególnie na Snapchacie.

 

[SQL] Normalizacja bazy danych – co to, po co i dlaczego to wszystko

Wiele z nas korzysta w swoich mniejszych czy większych projektach z baz danych. Niektórzy nie zwracają wielkiej uwagi na jej budowę i tworzą tabelę bez większego zastanowienia się nad sprawą. Dlatego w tym poście chciałbym wam powiedzieć co nie co o normalizacji baz danych.

Ale na początku trochę historii 😉

W latach .60 i .70 brytyjski informatyk Edgar Frank Codd  pracował nad zagadnieniami porządkowania danych i wydał pracę o nazwie A Relational Model of Data for Large Shared Data Banks (dostępną tutaj), w której przedstawił relacyjny model zarządzania bazami danych. Całość sprowadza się do 12 postultów Codda (link do przetłumaczonych postulatów). Na początku IBM zwlekał z wprowadzeniem jego rad w życie, jednak gdy konkurencja postanowiła zastosować się do wskazówek brytyjskiego uczonego, IBM nie mając wyjścia musiała zrobić to samo.

W tym wszystkim najważniejsza bez wątpienia jest sprawa normalizacji bazy danych.

Wyróżniamy 3 typy normalizacji:

  • 1NF (1 normal forms) – pierwsza postać normalna
  • 2NF – druga postać normalna
  • 3NF – trzecia postać normalna

W skrócie:

  • Tabela jest w 1NF kiedy posiada klucz podstawowy oraz wszystkie jej dane są elementarne.
  • Tabela jest w 2NF kiedy zarówno jest w 1NF oraz kiedy wszystkie jej atrybuty niekluczowe zalezą funkcyjnie od pełnego klucza.
  • Tabela jest w 3NF kiedy jest w 2NF oraz wyeliminowane zostały pola które nie zależą od klucza.

Nie będę podawał przykładów ponieważ w sieci jest na prawdę spoko materiałów na ten temat, każdy opisuje problem w prosty dla czytelnika sposób więc nie powinno być z tym problemu.

Jeżeli ktoś chce się zagłębić w ten temat to na końcu tego wpisu wrzucam kilka linków na ten temat.

Ale chciałbym powiedzieć co zyskujemy dzięki normalizacji.

Po pierwsze, unikamy redundancji danych – czyli nadmiarowości. Zamiast przechowywać dane powtarzające się w każdej tabeli, rozdzielamy je na osobne tabele i łączymy tabele kluczami obcymi. Zmniejsza nam to również ryzyko powstania niespójności wynikającej z nadmiarowości.

Dzięki normalizacji zyskujemy większą organizację danych. W każdej tabeli mamy dane które dotyczą jednego bytu rzeczywistego. W tabeli Klienci posiadamy dane o klientach a w tabeli Meble tabele o meblach. Tabele powinny nam jednoznacznie mówić co się w nich znajduje i jakich danych możemy się w nich spodziewać. Zyskujemy również zgodność danych w bazie, wiemy że w kolumnie telefon dane zawsze będą miały format np 000-000-000 i że jeden rekord to dokładnie jeden telefon a nie 3 lub 4 numery zapisane raz po przecinku a raz po dwóch spacjach.

Normalizacja daje nam również większą elastyczność. Możemy w łatwiejszy sposób rozszerzać naszą bazę bez potrzeby modyfikacji istniejących już tabel.

Dlatego warto poświęcić kilka minut na projekt bazy i zastanowienie się nad jej budową aby później nie musieć spędzać kilku godzin na modyfikacji bazy 😉

Obiecane linki

w języku polskim:

w języku angielskim:

Jak coś ważnego pominąłem lub pokręciłem w tekście to daj znać w komentarzu! Człowiek uczy się całe życie 🙂

Echo <br/>

[C#] Modyfikacja API i zakończenie etapu pierwszego

Za mną pracowity weekend, skończyłem pisać ważną część programu, ale mimo tego nadal masa kodu jest do napisania.

Aktualnie KINTER potrafi przenosić produkt z ERP do sklepu pracującego na silniku PrestaShop. W skład produktu wchodzą takie elementy jak: nazwa, opis, zdjęcie, cena, stan magazynowy oraz przypisanie do kategorii.

Z tym ostatnim miałem najwięcej problemów polegającym na tym że sklep nie pozwala na dodanie przez API kategorii z własnym numerem ID…

Oczywiście musiałem coś z tym zrobić, bo bez tego nie ruszyłbym z miejsca. Na początku musiałem wyedytować plik po stronie sklepu:

W pliku

WebserviceRequest.php

musiałem zakomentować taki fragment:

elseif ($this-&gt;method == 'POST' &amp;&amp; count($ids) &gt; 0) 
{
   $this-&gt;setError(400, 'id is forbidden when adding a new resource', 91);
   return false;
}

oraz w pliku

ObjectModel.php

w linii numer 460 musiałem zakomentować fragment:


if (isset($this->id) && !$this->force_id) {
unset($this->id);
}

Musiałem również zakomentować linię kodu w API do PrestaShopu – PrestaSharpie. Tam również w locie zmieniano ID na inne niż nasze własne.


public Entities.category Add(Entities.category Category)
{
long? idAux = Category.id;
//Category.id = null;
List&lt;PrestaSharp.Entities.PrestaShopEntity&gt; Entities = new List&lt;PrestaSharp.Entities.PrestaShopEntity&gt;();
Entities.Add(Category);
RestRequest request = this.RequestForAdd("categories", Entities);
Entities.category aux = this.Execute&lt;Entities.category&gt;(request);
Category.id = idAux;
return this.Get((long)aux.id);
}

W 4 linii mały komentarz załatwił sprawę. I od tej pory wszystkie kategorie przenoszą się bez problemów.

Następnym krokiem będzie stworzenie synchronizacji zamówień ze sklepu do ERP. Co będzie? Zobaczymy!

Echo <br/>

[C#] Jak masowo pobrać nazwy Snapów uczestników DSP?

Chciałbyś dodać wszystkich uczestników DSP 2017 do swojego Snapchata ale nie chcesz przeglądać każdego profilu po kolei? Więc jak to można szybciej zrobić?

Napisz sobie crawlera!

Crawler – taką nazwę noszą skrypty które przeszukają stronę pod względem interesujących programistę treści. Jedni kochają crawlery, drudzy wręcz nienawidzą. Dlaczego?

Bardzo często z crawlerów korzystają np. spamerzy którzy masowo przeczesują strony pod kątem adresów email lub numerów telefonów aby następnie rozsyłać spam itp. Programiści na potęgę wymyślają coraz to nowsze sposoby aby blokować crawlery na swoich stronach lub aby ukrywać przed nimi wrażliwe dane. Ale są też “dobre” crawlery które mogą zbierać treści z naszych stron dla wyszukiwarek internetowych albo zbierać linki i tworzyć na ich podstawie mapę strony.

Aby pokazać jak działa taki prosty skrypt pobierzemy listę loginów ze snapchata użytkowników DSP – oczywiście za zgodą Macieja Aniserowicza 😉

Jak to powinno działać?

  1. Wejdź na stronę z listą uczestników.
  2. Wejdź w profil każdego uczestnika
  3. Sprawdź czy w profilu podane jest nazwa snapchata
    1. jeżeli jest to wyświetl nazwę
    2. jeżeli nie – pomiń

No to do dzieła. Na początku rzućmy okiem na budowę strony uczestnicy.dajsiepoznac.pl/lista

Linki do poszczególnych profili są z miejscu zaznaczonym na screenie ( jak je znalazłem? Kliknąłem PPM tekst > Zbadaj, tak jest w przeglądarce Chrome, można też wcisnąć skrót CTRL+SHIFT + I)

Czyli aby otrzymać link powinniśmy pobrać zawartość atrybutu “href”. Można to zrobić np RegExpem którego osobiście nie znam, jakoś nie potrafię się tego nauczyć (może zamiast pisać aplikację w C# na DSP2017 powinienem właśnie coś z wyrażeniami regularnymi zrobić? 😉

Albo możemy zainteresować się paczką o nazwie Html Agility Pack która jest

an agile HTML parser that builds a read/write DOM and supports plain XPATH or XSLT 

Będzie więc pomocna przy pobieraniu interesujących nas danych ze strony www i dane to możemy pobrać za pomocą XPatha który służy do adresowania części dokumentu XML.

XPath możemy pobrać klikając PPM -tak jak na screenie poniżej.

Teraz w schowku mamy coś takiego:

/html/body/div[3]/h4[1]/a

Co to znaczy? Żeby lepiej wam to zobrazować rzućcie okiem na screena:

Jest to ścieżka prowadząca do miejsca w którym mamy linka do profilu.

Okej, teraz możemy przystąpić do pisania programu.

static void Main(string[] args)
{
 string URL = "http://uczestnicy.dajsiepoznac.pl/lista";
 string UsersXPath = "/html/body/div[3]/h4[1]/a";

 HtmlWeb web = new HtmlWeb();
 HtmlDocument doc = web.Load(URL);

 var hrefs = doc.DocumentNode.SelectNodes(UsersXPath)
 .Select(p => p.GetAttributeValue("href", ""))
 .ToList();

 foreach(string link in hrefs)
 {
 Console.WriteLine(link);
 }
}

W linii nr 9 pobieramy elementy do których prowadzi ścieżka XPath w zmiennej o nazwie UsersXPath.

Po uruchomieniu tego programu otrzymujemy jako wynik:

/profil/rafal-hryniewski

Czemu program wyświetlił nam tylko pierwszy link? A no dlatego że w naszej ścieżce XPath znajduje się element h4 z liczbą 1 w nawiasach. Oznacza on że nasz element znajduje się w pierwszym napotkanym elemencie h4. A przecież to nie prawda, ponieważ linki znajdują się i w drugim elemencie i trzecim itd itd. Więc zamiast liczby “1” wpisujemy tam gwiazdkę “*”.

Uruchamiamy zmodyfikowany program i sprawdzamy. Jest ok, mamy linki do każdego profilu.

Następnym krokiem będzie odwiedzenie tychże linków i pobranie następnego elementu jakim będzie login ze SnapChata.

Robimy to analogicznie jak robiliśmy z linkami do profili. Czyli pobieramy XPatha który będzie miał taką postać:

/html/body/div[3]/div[3]/div[2]/div/div[2]/div[2]/div[1]/a

Od razu powiem że powinniśmy zmienić to na:

/html/body/div[3]/div[*]/div[2]/div/div[2]/div[2]/div[1]/a

dlatego że nie każdy użytkownik opublikował post na swoim blogu sprawiając że ukrywa się sekcja z najnowszymi postami w profilu użytkownika.

Jako że nie każdy użytkownik podał login dodajemy warunek aby puste stringi nam się nie wyświetlały.

Finalnie, program przyjmie postać:


static void Main(string[] args)
{
string URL = "http://uczestnicy.dajsiepoznac.pl/lista";
string UsersXPath = "/html/body/div[3]/h4[*]/a";

HtmlWeb web = new HtmlWeb();
HtmlDocument doc = web.Load(URL);

var hrefs = doc.DocumentNode.SelectNodes(UsersXPath)
.Select(p => p.GetAttributeValue("href", ""))
.ToList();

string SnapchatXPath = "/html/body/div[3]/div[*]/div[2]/div/div[2]/div[2]/div[1]/a";

foreach (var link in hrefs)
{
doc = web.Load("http://uczestnicy.dajsiepoznac.pl" + link);

var snapchat = doc.DocumentNode.SelectNodes(SnapchatXPath).ToList();

HtmlNode node = doc.DocumentNode.SelectNodes(SnapchatXPath)[0];
if (node.InnerText != null && node.InnerText != "")
{
Console.WriteLine(link + " : " + node.InnerText);
}
}

Console.ReadKey();
}

Uruchamiamy go i sprawdzamy czy działa jak należy.

Otrzymujemy w prostym formacie link do profilu i login do snapa każdego użytkownika. O to nam chodziło!

Możemy teraz przystąpić do dodawania wszystkich na snapchacie 😀 Ale, czy ktoś byłby w stanie oglądać wszystkich każdego dnia? 😉

 

Echo <br/>

Sprawozdanie z postępu prac nad KINTEREM

Dziś poniedziałek, za mną dosyć pracowity weekend. Na blogu pojawił się dłuższy wpis o SQL Injection (niestety jedynie w języku polskim – na dniach postaram się przetłumaczyć!), udostępniłem również swój bardzo mały program BUDZET na GitHubie no i oczywiście zmieniłem wygląd bloga. Mam nadzieję że aktualny wygląd nie odstrasza, mi osobiście się w chu.. ekhm bardzo mi się podoba 😀

Dodałem również do menu odnośnik do strony Projekty na której będę umieszczał wszystkie moje programy i inne kawałki kodu którymi chciałbym się podzielić ze światem. A  jakaś zbłąkana dusza zaczerpnie z nich jakichś gotowych rozwiązań. Po to są!

Dobra, pora napisać coś o projekcie konkursowym. KINTER, jak już wiadomo, będzie synchronizował produkty i inne elementy z oprogramowania typu ERP firmy StreamSoft z oprogramowaniem sklepu internetowego PrestaShop.

Dziś mogę się pochwalić że poczyniłem niemałe postępy. Program aktualnie potrafi przenosić produkty, na co składają się: nazwa, cena, opis, stan magazynowy oraz zdjęcia.

Z tym ostatnim miałem najwięcej problemów. PrestaSharp, czyli nieoficjalny klient do obsługi API PrestaShopu, wywalał mi błąd w którym informował mnie że nie może znaleźć funkcji z pakietu RestSharp z którego korzysta. Męczyłem się z tym przez dobre 2 godziny, nie mogąc znaleźć problemu. W końcu gdzieś na francuskim forum “doczytałem” żeby spróbować ręcznie dokonać downgrade do wersji 105.1.0 – pomogło! Na dziś koniec. Jutro zabieram się za dopracowanie tej funkcjonalności.

W ogóle, dziwię się sam sobie. W piątek napisałem trochę kodu, a sprawdzając go dzisiaj już dokonywałem refaktoryzacji. Jak to dwa dni czytania i oglądania różnych poradników i ebooków potrafią zmienić sposób myślenia. Przy tej okazji polecam poradnik Piotra Gankiewicza “Becoming a software developer” (mimo angielskojęzycznej nazwy, całość tłumaczona po polsku). Piotr również aktywnie udziela się na Snapchacie – wszystkie informacje znajdziecie bez problemu na jego stronie.

Tak więc poniedziałek jak najbardziej zaliczam do tych udanych i całkiem nie najgorszych poniedziałków (chyba że coś się dziś stanie, ale na litość boską, jest już godzina 21 i raczej nikt i nic nie popsuje mi tego dnia 😉 )

Echo <br/>

[SQL Injection] Przykład ataku na stronę www

W dzisiejszym poście chciałbym wam pokazać jak poprzez drobne zaniedbanie programisty można “włamać” się na stronę www.

Przypominam że wszystkie informacje publikowane są tutaj jedynie w celach edukacyjnych. Nie zalecam stosowania ich niezgodnie z prawem.

Na tapetę weźmiemy błąd który nazywamy SQL Injection (z ang. wstrzykiwanie – tutaj własnego kodu SQL).

Błąd polega na tym że wprowadzamy dane których programista się nie spodziewał np. zamiast liczby podajemy znak specjalny.
Oczywiście w tym miejscu powinna następować jakaś walidacja wprowadzanych przez użytkownika danych jednak po tym co widać w internetach w tej kwestii programiści darzą użytkowników dużym zaufaniem.

Błąd ten jest to jeden z popularniejszych błędów, bardzo łatwo natknąć się na stronę która jest na niego podatna. Jest to trochę niepokojące, ponieważ na prawdę nie trzeba się bardzo wysilać aby ten błąd załatać, a może od doprowadzić do katastrofalnych skutków.

Dlatego zabieram się do opisania tego wraz ze screenami i wytłumaczeniem co i jak, krok po kroku.


Załóżmy że chcemy uzyskać nieautoryzowany dostęp (czy może bardziej kolokwialnie “włamać“) do strony o tematyce sportowej bo jej administrator obraża tam naszą ukochaną drużynę Zjednoczeni Sosnowiec.
Wchodzimy na stronę i widzimy kilka newsów i nawet publiczny link do logowania do – prawdopodobnie – panelu administracyjnego.
Naszym celem będzie zdobycie danych logowania i popsucie strony niedobrego dziennikarza sportowego.

Ale sprawdźmy na początek czy administrator nie loguje się danymi takimi jak admin:admin. Kto go tam wie?!

No nie tym razem, ale warto było spróbować 😉

Rzućmy okiem na stronę główną i przeczytajmy sobie pierwszy news. Klikając na tytuł newsa strona przekierowuje nas do pełnej treści.

Zauważmy że w pasku adresu pojawia się taki fragment:

aktualnosci,2.html

Możemy się domyślać że 2 to ID danego newsa.
Podstawiając tam inną liczbę, dajmy na to 3 – wyświetla nam się inny news – logiczne.

W tym właśnie miejscu programista spodziewa się liczby. Sprawdźmy co się stanie jak dopiszemy tam jakiś znak.

Wyskoczył nam błąd o treści:

Warning: mysql_num_rows() expects parameter 1 to be resource, boolean given in C:\xampp\htdocs\responsive-blog-template\index.php on line 132

Oznacza on że zapytanie które zostało wykonane do bazy danych jest nieprawidłowe, parametr jest błędny.
Co mogliśmy popsuć dodając jeden znak?

Musimy wyobrazic sobie jak wygląda zapytanie do bazy danych pobierającego newsa.
Zapewne może to być coś w tym rodzaju:

SELECT title, content, author, date FROM news WHERE id = 2;

Jak wygląda zapytanie z dodanym przez nas znakiem? Ano tak:

SELECT title, content, author, date FROM news WHERE id = 2';

Hmm a jak byśmy dodali coś w stylu

AND 1=1

Zapytanie przybrało by taką, hipotetyczną, formę

SELECT title, content, author, date FROM news WHERE id = 2 AND 1=1;

Czyli news powinien nam się poprawnie wyświetlić ponieważ zapytanie jest poprawne. Sprawdźmy.

A w drugą stronę, jak dopiszemy AND 1=0?

SELECT title, content, author, date FROM news WHERE id = 2 AND 1=0;

Ukazała nam się pusta strona, ponieważ zapytanie oczywiście nie spełnia warunków podanych w WHERE bo 1 nigdy nie będzie równe 0 i zapytanie zwróci 0 wyników.

Czy to jest błąd SQL Injection? Tak, jest i już wyjaśniam jak możemy go nadużyć.

Ale najpierw muszę wytłumaczyć jak działa słowo kluczowe UNION w SQL.
UNION pozwala połączyć wyniki dwóch zapytań w jeden wynik.
Spójrzmy na przykład:

Połączmy więc oba wyniki zapytaniem:

SELECT * FROM pracownicy UNION SELECT * FROM zarobki;

Uh, wyskoczył nam jakiś błąd:

Mówi nam on o tym że UNION można użyć jeżeli tabele użyte w zapytaniu mają po tyle samo kolumn. W naszym przykładzie tabela “pracownicy” ma dwie kolumny, a “zarobki” 3.

Jak można to obejść? Zmieniając zapytanie w taki sposób:

Zapytanie wykonało się poprawnie.

Wrócmy do naszej strony. Znając działanie UNION możemy zmodyfikować zapytanie do bazy danych dodając tam swoje zapytanie! Czy to nie brzmi świetnie?! 😀

Skoro więc UNION można użyć tylko z tabelami które mają tyle samo kolumn, należy się dowiedzieć ile kolumn używa zapytanie na stronie.

Wystarczy że “policzymy” kolumny. Robimy to w banalny sposób. Do zapytania dopisujemy liczby. Zaczynami od jednej, sprawdzamy wynik i w miarę potrzeby dopisujemy kolejną liczbę i znów sprawdzamy wynik.

Wpisujemy 1,2,3,5 itd aż do skutku.
Widzimy że zapytanie na pewno nie ma 6 kolumn, ma ich więcej więc liczmy dalej i przy 10… voilà!

Dla lepszej widoczności ukryjmy news który się wyświetla poprawnie. Robimy to poprzez manipulację pierwszego zapytania dodając do niego wspomniane wcześniej AND 1=0 które sprawia że pierwsze zapytanie zwróci 0 wyników.

Dobra, mamy policzone już kolumny w zapytaniu. Jest ich 10. Co dalej?

Dalej możemy podstawić pod odpowiednie liczby w zapytaniu na przykład zmienne systemowe z SQL. Wpisując @@version dowiemy się jaka wersja bazy danych uruchomiona jest na serwerze. Zmiennych jest wiele i polecam zajrzeć do manuala (link na końcu postu) lub będąc zalogowanym do bazy MySQL możemy wykonać polecenie

SHOW VARIABLES

Możemy też zamiast zmiennych wpisać tam funkcje. Przeglądając manual interesującą z perspektywy atakującego może być funkcja

database()

wyświetlającą nazwę bazę danych z której korzysta strona.

Pamiętając że naszym celem jest zalogowanie się do panelu administracyjnego powinniśmy pobrać login i hasło admina.
Jedno ale – nie znamy nazwy tabeli w której przechowywane są takie dane. Należy więc się dowiedzieć jakie dane są w naszej bazie!

Wpisując wspomnianą wyżej funkcję database() otrzymujemy:

Wiemy że baza danych nazywa się “sqli”. Chcemy więc pobierać wszystkie tabele z bazy o nazwie sqli.
Jak to zrobić?

Z pomocą przychodzi nam taka specjalna tabela która nazywa się

information_schema

(znów odsyłam do manuala – warto rzucić okiem ;))

W tej tabeli przechowywanych jest wiele informacji np. lista tabel oraz kolumn w tabelach na serwerze. To nas interesuje najbardziej!

Czytając niezbędnik hackera (czyt. manual) dowiadujemy się ze aby wyświetlić tabele należy wykonać zapytanie:

SELECT table_name FROM information_schema.tables;

To nam wyświetli jednak wszystkie tabele ze wszystkich baz, a nas interesują tabele z bazy o nazwie “sqli”.
Dodajmy więc odpowiedni warunek WHERE:

SELECT 1,table_name,3,4,5,6,7,8,9,10 FROM information_schema.tables WHERE table_schema='sqli';

No i mamy to o co nam chodziło!
Dowiedzieliśmy się że w bazie “sqli” są dwie tabele: news i users!

Pobierzmy więc kolumny z tabeli users.
Analogicznie jak w przypadku tabel – zapytanie będzie wyglądało tak:

SELECT column_name FROM information_schema.columns WHERE table_name = 'users'

Mając już kolumny możemy wyświetlić wszystkie rekordy z tej tabeli.

Polecenie:

SELECT id,login,password,role FROM users;

Aby lepiej nam się przeglądało dane możemy je ładniej wyświetlić z pomocą funkcji SQL concat() która jak się można domyślić służy do konkatenacji czyli łączenia wyrażeń.

SELECT 1,concat(char(32),login,char(32),password,char(32),role),3,4,5,6,7,8,9,0 FROM users;

char(32) w zapytaniu wyświetla znak którego kod ASCII wynosi 32 – w tym przypadku to po prostu spacja którą wstawiłem dla czytelniejszego wyniku zapytania 😉

Dobra, dowiedzieliśmy się że użytkownikiem który jest adminiem jest konto z loginem “adinistrator” i hasłem “tajnehaslo123”.

Hasła najczęściej nie są przechowywane w plain texcie ale zakodowane za pomocą pewnych funkcji np MD5 lub SHA1 itp.
Ale nie o haszach tutaj mowa więc dla uproszczenia hasło zostało zapisane w bazie jawnym tekstem.

Sprawdźmy więc czy danymi z tabeli mozna się zalogować do panelu.

Juppi! Zalogowaliśmy się i w końcu możemy usunąć brzydkiego newsa!

Oczywiście to tylko przykład, w prawdziwym życiu najczęściej osoba włamująca się szuka miejsca gdzie można wgrać tzw. php shell (lub jak kto woli – web shell), czyli coś za pomocą czego ma pełen dostęp do serwera na którym postawiona jest strona.


Widzimy więc do czego z pozoru niewinny błąd – lub może nawet było to tylko niedopatrzenie programisty – może doprowadzić. Warto więc sprawdzać takie newralgiczne miejsca w kodzie, w którym dajemy użytkownikowi dostęp do manipulowania danymi.

Zakładajmy że użytkownik na pewno wpisze w danym polu coś całkiem innego niż powinien.
Jeżeli spodziewamy się liczby to załóżmy że ktoś wprowadzi tam literę, znak specjalny, może liczbę ujemną, a może liczbę większą niż typ naszej zmiennej.

Weźmy pod uwagę wszystkie scenariusze dzięki temu unikniemy niepotrzebnych nieprzyjemności.

Jak wyglądał kod strony z newsami?

$sql = "SELECT * FROM news n INNER JOIN users u ON u.ID = n.author_id WHERE n.id=$id";

W miejscu n.id = $id nie powinniśmy bezpośrednio podawać danych od użytkownika. Zmienna $id powinna przez nas zostać wcześniej sprawdzona jakąś funkcją wykrywającą i usuwającą niepożądane znaki.

Na tej stronie błąd może również występować w miejscu logowania się do panelu administracyjnego.
Zobaczmy czy nie dało się tam zalogować bez wykonywania opisanych wyżej czynności.

Wyobraźmy sobie jak może wyglądać zapytanie sprawdzające czy użytkownik podał poprawny login oraz hasło i czy jest on administratorem.

SELECT id FROM users WHERE login = '$LOGIN' AND password = '$HASLO' AND role = 'admin';

W miejscach $LOGIN i $HASŁO wstawiane są wartości podane przez użytkownika.
Jakie wartości należy wstawić aby zapytanie zwróciło niepusty wynik? 😉

Co powiecie na wpisanie w miejscu loginu tego:

' OR role='admin'#

Zapytanie będzie wyglądało mniej więcej tak:

 SELECT id FROM users WHERE login = '' OR role='admin'#' AND password = '' AND role = 'admin';

Znak # w SQL oznacza początek komentarza, czyli wszystko co będzie za nim będzie zignorowane czyli serwer sql potraktuje to jako:

 SELECT id FROM users WHERE login = '' OR role='admin';

Sprawdzamy:

O zgrozo! Zalogowaliśmy się 😀

Na zakończenie tego artykułu podam że strony które mogą być podatne na ten błąd można znaleźć w google wpisując w wyszukiwarkę frazę:

inurl:?id=

Jest to tzw. google dork. Ale o tym napiszę kiedy indziej 😉

Wpisz i sprawdź czy nie ma tam Twojej strony, może warto ją zabezpieczyć? 😉

Bibliografia:
https://dev.mysql.com/doc/refman/5.7/en/information-schema.html
https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html
https://dev.mysql.com/doc/refman/5.7/en/information-functions.html
https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat
https://pl.wikipedia.org/wiki/ASCII#Tabela_kod.C3.B3w_ASCII
https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_char
https://en.wikipedia.org/wiki/Hash_function
https://haker.edu.pl/2015/09/20/co-to-jest-php-shell/