Michał Matyas

Jan 15

Znajdowanie najbliższej liczby w MySQL

Podczas pracy nad aktualnym zleceniem (nie wiem czy mogę się nim chwalić, dlatego chwilowo cicho-sza) potrzebowałem znaleźć sposób, na pobieranie rekordu z bazy najbliższego pełnej godziny. Problem polegał na tym, że rekordów w bazie mogło być tysiące lub więcej, więc zwykłe pole typu DATE / TIMESTAMP było niewystarczające - jak sprawdzić, czy rekord o 11:59:59 był bliżej pełnej godziny niż 12:00:00, skoro mamy jeszcze 1000 mikrosekund pomiędzy? A co, jeśli ten pierwszy został dodany o 11:59:59.999, a ten drugi o 12:00:00.500 ?

Rozwiązaniem problemu było zapisywanie w polu typu DOUBLE wartości microtime() z PHP. Pojawiał się jednak nadal problem znalezienia najbliższej wartości pełnej godziny. Klient podkreślił, że pobieranie musi być “dookoła” danego czasu, a nie tylko najbliżej + pierwszy o pełnej, jak to miało miejsce w innym projekcie tego typu, który realizowałem.

W końcu bo wielu minutach Googlania i eksperymentach, znalazłem ostateczne rozwiązanie, które przedstawiam poniżej. Nie jest do końca moje, ale może komuś się przyda:

(
   SELECT * FROM :table WHERE timestamp > :timestamp 
   ORDER BY timestamp ASC LIMIT 1 
) 
UNION 
( 
   SELECT * FROM :table WHERE timestamp <= :timestamp 
   ORDER BY timestamp DESC LIMIT 1 
) 
ORDER BY ABS(timestamp - :timestamp) 
LIMIT 1

Nawiasem mówiąc, w module Database do Kohany 3.0 nie ma jeszcze obsługi UNION, ale widziałem ją w repozytorium GIT-a dla 3.1, więc jeśli korzystamy z ORM-a, chwilowo można się salwować takim rozwiązaniem:

$query = DB::query(Database::SELECT, 
  "( SELECT * FROM :table WHERE timestamp > :timestamp ". 
     " ORDER BY timestamp ASC LIMIT 1 ) ".
  "UNION".
  "( SELECT * FROM :table WHERE timestamp <= :timestamp ".
     "ORDER BY timestamp DESC LIMIT 1 ) ".
  "ORDER BY ABS(timestamp - :timestamp) LIMIT 1")
->parameters(
  array(
      ":table"     => "elements",
      ":timestamp" => 1295090875
      )
->as_object('Model_Element')->execute();

Moje kursy dot. Kohany 2.3 są już na tyle nieaktualne, że stwierdziłem, że daję sobie spokój z tą serią, była kochanym potworkiem, ale nadal potworkiem. Ostatnio zakochałem się w Ko3, więc może jak znajdę więcej czasu i będę miał coś ciekawego do napisania, zrobię kilka wpisów.

A dla zainteresowanych nie tylko technikaliami - przekonałem się w końcu do ORM-a. Wheee!

Jun 22

Dlaczego sudo jest niebezpieczne dla użytkownika

W systemach GNU/Linuksowych od dłuższego czasu zarówno developerzy jak i użytkownicy zahłystują się rozwiązaniem skopiowanym z Mac OS X zwanym sudo. W promowaniu tego rozwiązania długo przodowało Ubuntu, jednakże w chwili obecnej od standardowego użytkownika root odchodzi coraz więcej dystrybucji, jak choćby Debian czy Ubuntowate.

Zdawać by się mogło, że sudo to rozwiązanie idealne: wygodne, proste, wymaga interakcji użytkownika (wpisanie hasła) w celu wykonania czynności administratorskich. Jest jakby odpowiedzią na logowanie się z poziomu usera na konto administratora w systemie Windows XP, jednakże pozbawione jest jego wad.

Czyżby?

“Ja nie chcę myśleć!”

Niestety nie oszukujmy się, przeciętny użytkownik komputera nie myśli o tym co robi. On nawet nie ma zamiaru myśleć, on chce po prostu korzystać ze swojego narzędzia. Tak samo jak kierowca niekoniecznie interesuje się tym, jak dokładnie działa linka hamulcowa w jego samochodzie, tak i średnio wysmażony żółtodziób chce jedynie widzieć ikonki, przeglądarkę i NK, a nie to co jest “pod maską”. Nie jest to złe, to raczej uboczny efekt popularyzacji informatyki i komputerów wśród “maluczkich”. Wszelkiego rodzaju graficzne interfejsy dążą do minimalizacji wymagań stawianych użytkownikowi.

Ma to też swoje wady.

Jak wszyscy wiemy, wspomniany wcześniej Windows XP od 2001 roku króluje na biurkach i bardzo niechętnie oddaje swoje pole systemowi Windows 7 (Viście wstydu oszczędzę). W systemie tym instalacja większości oprogramowania spod konta normalnego użytkownika kończyła się wywaleniem błędu niedostatecznych uprawnień. Z tego powodu normalni użytkownicy korzystali po prostu domyślnie z konta administratora, żeby system się raz na zawsze ‘odczepił’. Efekty? Wirusy, trojany, rootkity, malware i całe inne cholerstwo, które dostawało pełen dostęp do systemu, zamiast do wydzielonego konta usera.

W przypadku systemów Linuksowych takiego problemu nie ma, gdyż konto roota było domyślnie zabronione w X11. Aby zainstalować jakiś program należało podać hasło administracyjne i dopiero przejść dalej. Sudo w założeniu miało ten proces uprościć jeszcze bardziej, dzięki czemu użytkownik nie musiał się przejmować ‘fruwającymi dookoła okienkami’ - jedno podanie hasła załatwia sprawę dla danego programu niezależnie od ilości jego wykonań.

I tutaj właśnie dochodzimy do sedna problemu.

“A rób se pan co chcesz”

W momencie podania hasła poleceniu sudo, gksu lub kdesu, przyznane prawa zostają zapamiętane. Dzięki temu w teorii wielokrotne zastosowanie sudo w konsoli (lub gksu/kdesu w GUI) nie będzie wymagało ponownego podawania hasła. Bezpieczeństwo ustępuje wygodzie.

Jeden z proponentów sudo, gdy usłyszał mój zarzut o możliwości wykorzystywania tego stwierdził, że sudo posiada mechanizm sprawdzający, z jakiej konsoli został wywołany i przydziela uprawnienia tylko w danym obszarze.

Guzik prawda. Wpisanie hasła do sudo w GNOME Terminal skutkuje dostępem do roota z poziomu TTY.

Już w listopadzie 2009 na blogu Bobiko przedstawiłem proof-of-concept1 prostego skryptu, który wykorzystywał sudo do zdobywania praw administracyjnych. Kod nie jest skomplikowany, nie jest to shellcode i tak naprawdę składa się z idiotycznie prostych poleceń:

#!/bin/bash
$(while true; do
    sudo -n bash -c '
        if [ ! -e /etc/hacked ]; then
            echo lol > /etc/hacked;
            useradd -p 'aaUw.ra0Wbf0s' lol;      
            echo "lol   ALL=(ALL) ALL" >> /etc/sudoers;
        fi' 2>/dev/null >/dev/null;
    sleep 5;
    done)&

Osoby, które pisały kiedykolwiek w bashu powinny już rozumieć co ten kod robi. Dla laików wyjaśnienie: wywołuje on w tle podprogram w formie skryptu bashowego, który próbuje - korzystając przy tym z sudo - sprawdzić czy istnieje plik /etc/hacked, jeśli nie to go utworzyć, stworzyć nowego użytkownika lol z hasłem lol, po czym dodać go do użytkowników z uprawnieniami sudo. Pomiędzy próbami ma odczekiwać 5 sekund, by nie wzbudzić podejrzeń.

Dalej łatwo się domyślić - ktoś wchodzi sobie grzecznie na ‘ustawiony’ komputer za pomocą ssh, za pomocą sudo su uruchamia roota i hulaj dusza, piekła nie ma.

Skrypt ten można dokleić do dowolnego programu, który zostanie wykonany przez użytkownika. Może to być dosłownie cokolwiek, tak długo jak jest plikiem wykonywalnym (a mało to w sieci skryptów ‘dla n00bów’? mało to gier w formacie .run, który jest gzipowanym archiwum z dołączonym skryptem?). Dodatkowy kod może również informować na maila o pozytywnym włamaniu lub przygotowywać już poletko pod botnet. Sytuacja identyczna jak za czasów Windowsa XP, z tą różnicą, że niepotrzebne jest konto administratora, wystarczy ZU.

A wszystko to dzięki idiotycznemu zaufaniu do użytkownika, który - jak już mówiłem - nie chce, nie ma zamiaru i nigdy nie będzie myśleć.

1 - kod w tym wpisie został lekko poprawiony i zmodyfikowany w celu pokazania rzeczywistego wymiaru zagrożenia. Informacje nt. tworzenia konta za pomocą useradd znalezione na Stack Overflow

Feb 02

“ASAP is poison” — From back cover of 37 signals’ new book, Rework

Jan 21

Podstawy frameworka Kohana, część 2

W poprzedniej części “Podstaw” pisałem o strukturze frameworka, podstawowej konfiguracji i uruchomieniu kohanowego odpowiednika “Hello World”. W tej części skupimy się na konkretniejszej konfiguracji naszego podstawowego projektu, nauczymy się jak wygląda kontroler, model i widok i nabierzemy pojęcia jak właściwie to wszystko powinno być ze sobą posklejane.

Więcej na temat konfiguracji

Wspominałem już, że większość plików konfiguracyjnych znajduje się w system/config, a tylko te, które będziemy zmieniać kopiuje się do application. Wbrew pozorom jest to dobre rozwiązanie i nie warto każdego projektu zaczynać od skopiowania wszystkich plików w razie ewentualnej edycji, chociażby dlatego, że nie z całej funkcjonalności będzie wykorzystywana w każdym projekcie. Przykładowo prosty skrypt blogowy raczej nie potrzebuje modułu Payment albo własnej konfiguracji helpera Inflector.

Są jednak takie pliki konfiguracyjne, które śmiało możemy przerzucać bezpośrednio do application/config zaraz po rozpoczęciu pracy nad projektem. Mówiłem o nich już, ale powtórzę - database, config, locale, routes. Nie będę opisywał wszystkich wymienionych plików, ponieważ są same w sobie odpowiedno dobrze pokomentowane (zwłaszcza ten drugi). Skupię się tylko na ostatnim z nich, gdyż jest najciekawszy.

Routes.php, niepozorna nazwa, jednakże bardzo często będzie to najważniejszy plik w całym projekcie. Odpowiada on jak sama nazwa wskazuje za ‘routing’ czyli przekierowywanie odpowiednich adresów do odpowiednich kontrolerów i ich metod. Konstrukcja samego pliku jest bardzo prosta: składa się on z kilku, czasem nawet do kilkudziesięciu przy bardziej skomplikowanych projektach elementów tablicy $config. Zasada jest taka - klucz tablicy to wyrażenie regularne, którym zostanie potraktowany adres, a jej wartość to wewnętrzne URI, do którego zostanie przekierowane pasujące żądanie. Jest też pozycja specjalna, _default, oznaczająca stronę, która ma zostać wyświetlona gdy w adresie nie znajduje się żaden kontroler.

Słowo o wewnętrznym URI - w najprostszym przypadku jest to po prostu konstrukcja kontroler/metoda/zmienna1/…/zmiennaN. Będę o tym mówił nieco szerzej już za chwilę, chwilowo po prostu przyjmijcie, że to tak działa.

W kwestii routingu należy pamiętać, że ważna jest kolejność z jaką przypisujemy wartości. Kohana nie poukłada ich niestety za nas sama, musimy więc pamiętać, by najbardziej ogólne klucze umieszczać najniżej. Posłużę się przykładem z własnego projektu - serwis posiada podstrony z adresem generowanym na podstawie tytułu w bazie danych. Na strony wchodzi się poprzez adres.pl/tytul-podstrony (zauważcie brak widocznego ID - takie było życzenie klienta), jednakże niektóre z podstron są generowane przez odpowiednie kontrolery. Rozwiązałem to przypisując tym kilku kontrolerom routing zbliżony do domyślnego, a resztę traktując bardzo ogólnym wyrażeniem regularnym. Gdybym zrobił to odwrotnie, podstrona admin zostałaby złapana do klucza ogólnego i przekierowana jako main/index/admin, a tego nie chciałem. Może kod wyjaśni więcej:

    $config['_default'] = 'main';
    $config['admin'] = 'admin/index';
    $config['admin/(.+?)'] = 'admin/$1';
    $config['ajax/(.+?)'] = 'main/ajax/$1';
    $config['(.+?)'] = 'main/index/$1';

Jak widać po drugiej linijce, routowanie można wykorzystać również w sytuacji gdy chcemy uniknąć wyświetlania w adresie kontrolera index(). W przypadku bardziej skomplikowanego kodu stosuje się metodę, którą przedstawię za moment.

Tworzenie kontrolera oraz widoku

Przebrnęliśmy już przez podstawy konfiguracji więc czas zabrać się za kontroler. Istnieją dwa sposoby tworzenia kontrolerów w Kohanie - za pomocą rozszerzania klasy Controller oraz drugi, dodatkowy - za pomocą klasy Template_Controller.

Zanim przejdę jednak do opisywania bardziej zaawansowanych aspektów, wrócmy do podstaw. Jak już wiemy, kontroler służy do kontrolowania tego całego bałaganu, który by się utworzył, gdybyśmy trzymali kod w jednym miejscu. Przyjmuje on od użytkowników różne prośby o dane (czy to POST, czy to GET, czy nawet XHR), idzie do modelu, stuka w szybkę, prosi o to i o to, a następnie wraca, podaje to widokowi, a ten wyświetla użytkownikowi to, czego chciał się dowiedzieć. Kontroler w Kohanie zawsze dziedziczy z klasy Controller, bezpośrednio lub pośrednio, powinen też posiadać metodę index(), która wykona się po wejściu na adres kontrolera. Kolejne podstrony danego kontrolera to właśnie kolejne metody, a nazwa metody jest częścią składową adresu (o czym było przed chwilą). Kontroler sam z siebie nie zwraca odpowiedniego widoku, od tego jest osobna klasa - View. O jej wykorzystaniu powiem w dalszej części tego tekstu. Teraz trochę o drugiej (poza bezpośrednim dziedziczeniem z Controller) metodzie tworzenia kontrolerów.

klasa abstrakcyjna - klasa, której instancji nie można utworzyć, ale może być dziedziczona przez normalne klasy. Nie należ jej mylić z interfejsem, który dostarcza jedynie nazwy metod wewnątrz klasy, ale nie może posiadać żadnego kodu.

Przede wszystkim różnice - Template_Controller jest klasą abstrakcyjną, więc nie może zostać bezpośrednio wywołany. Został więc umieszczony razem z innymi kontrolerami, żeby nie tworzyć dodatkowych regułek w samym frameworku, przez co robi trochę bałaganu (który można uniknąć podkatalogami, o czym niżej). Tak naprawdę nie różni się on wiele od standardowego kontrolera i dziedziczy również z Controller, jednakże posiada dodatkową zaletę, którą przydaje się wykorzystywać - pozwala na wykorzystanie gotowego szablonu i rozdzieleniu strony na mniejsze elementy.

Przedstawiony powyżej rysunek ukazuje jeden ze sposobów tworzenia szablonu strony. Osoby korzystające wcześniej z systemów szablonów typu Smarty zapewne już wiedzą do czego zmierzam. Podczas pisania pierwszych aplikacji zapewne korzystaliście z najczęściej pokazanej w kursach metody, tzn. include’owaliście najpierw nagłówek strony, następnie umiesczaliście treść tego co chcecie uzyskać, a na końcu doklejaliście stopkę.

Nie chcę demonizować tego rozwiązania, ale posiada ono szereg poważnych wad. Przede wszystkim wprowadza nam dodatkową warstwę do martwienia się - jeśli chcemy zmienić szablon tylko dla jednego kontrolera to żaden problem, tworzymy header_2.tpl i footer_2.tpl i sprawa załatwiona. Co jednak jeśli chcemy zmienić potem ten szablon w połowie widoków?

Oczywiście można to hackować mniej lub bardziej paskudnymi sposobami, ale pozostaje to jednak hackiem. Poza tym przy tworzeniu kolejnych elementów szablonu okazuje się, że kod staje się niesamowicie powtarzalny - cały czas operujemy przecież na podobnych zestawach elementów. Gdy mamy złożony cały serwis, dla przykładu sklep internetowy, wyświetlanie jednego produktu jest powtórzone w kilkunastu miejscach (np. w kontrolerze do obsługi wyświetlania po kategorii czy w wyszukiwarce). Wprowadzanie jakichkolwiek zmian w wyglądzie, który przewija się przez całą stronę w kilkunastu plikach jest cholernie denerwujące.

Z tego też powodu ktoś wymyślił, a inni podchwycili znacznie lepszy sposób tworzenia stron. Można to porównać do papierowej wycinanki - na obrazku element podpisany layout jest kartką papieru, section to duże skrawki kolorowych kartek, a box to mniejsze, odrębne kawałeczki. Jeśli zamiast posklejać obrazek po prostu położymy na nim szybę (czyli zamiast wsadzić wszystko w jeden plik poskładamy sobie szablon z tych kawałków) to późniejsze wykorzystanie tych samych elementów do stworzenia nowej pracy o podobnej kompozycji nie nastręcza większych problemów.

Niecierpliwym w końcu wyjaśniam - layout jest szkieletem strony HTML, zawiera DOCTYPE, sekcję HEAD gdzie wpisane są adresy do styli i skryptów, ogółem najbardziej podstawowy szablon. Może zawierać też elementy powtarzające się na wszystkich stronach, np. logo projektu, górne menu (chociaż takie rzeczy lepiej zrobić… dobra, bez dywagacji, zaraz napiszę). Section to właściwa część strony, układ elementów, ich dokładniejsze rozmieszczenie, wszystko to co tworzy stronę. Box to właśnie te małe skrawki, które będą częściej wykorzystane. Nie należy jednak popadać w paranoję i ”boksować” wszystkiego. Tabelka, która znajduje się na jednej stronie kandydatem na box nie jest, ale już na przyład wygląd wpisu na blogu owszem.

Ufff, przebrnęliśmy przez widok, jak zwykle przeskakując od tematu do tematu. Wróćmy do kontrolera - mówiłem już, że Template_Controller pozwala na wykorzystanie szablonów, a w konsekwencji i całego tego skomplikowanego układu, o którym mówiłem powyżeej. Znajduje się on w system/controllers, ale nie radzę go kopiować, a wykorzystywać metodę, za pomocą której został stworzony.

Przypomnienie dla nieuważnych w nauce - konstrukcja parent pozwala na odwołanie się do metody rodzica danej klasy. Jeśli nadpisujemy którąś z metod klasy, z której dziedziczymy, ale chcemy jedynie rozszerzyć jej możliwości, możemy wykonać kod z metody rodzica za pomocą parent::nazwametody(). Najczęściej stosuje się to przy nadpisywaniu kontruktora, ale nie tylko.

Przykład - mamy panel administracyjny. Chcemy, żeby po wejściu do panelu użytkownik bez hasła był przekierowywany na inną stronę. Teoretycznie - proste, wystarczy wrzucić to w __construct() i po sprawie. Co jednak jeśli panel składa się z więcej niż jednego kontrolera? Powtarzanie kodu jest złem, co mówi nam ważna zasada DRY (Don’t Repeat Yourself). Znacznie lepszym rozwiązaniem jest stworzenie w tym momencie abstrakcyjnej klasy Admin_Template_Controller dziedziczącej z Template_Controller i posiadającej w konstruktorze wymieniony kod (i co bardzo ważne parent::__construct() na początku lub końcu - łatwo o tym zapomnieć). Możemy wykorzystać tą sytuację również do generowania górnego menu, o czym wspominałem przed chwilą.

Jeśli chodzi o kontrolery w Kohanie to jest jeszcze jedna, bardzo przydata funkcjonalność, o której w dokumentacji jest tylko szybka wzmianka, więc łatwo ją przeoczyć. Otóż Kohana nie zabrania tworzenia katalogów w controllers, a nawet wspiera korzystanie z nich. Przydaje się to bardzo przy grupowaniu właśnie takich rzeczy jak panel administracyjny. Wystarczy stworzyć katalog admin, wrzucić do niego kontrolery od panelu administracyjnego i utworzyć dodatkowo w głównym katalogu kontroler admin.php zawierający to, co pojawi się po wejściu na adres strona.pl/admin . Kontrolery w katalogu będą uruchamiane po wejściu w admin/nazwakontrolera i zachowywały się od tego miejsca jak na grzeczne kontrolery przystało. Również wewnętrzne URI będzie skonstruowane w ten sam sposób, o czym wspominałem wyżej - konstrukcja zmieni się po prostu na katalog/kontroler/metoda/zmienna1/…/zmiennaN.

Jak już pewnie zauważyliście, staram się unikać listingów kodu. Zaciemniają one obraz i powodują bezmyślne kopiowanie, ponieważ prawdziwa nauka powinna być na przykładach. Z tego też powodu kodu kontrolera czy widoku nie umieszczam tutaj nigdzie i go nie tłumaczę, ponieważ bez podstawowej wiedzy o obiektowym programowaniu z frameworkami nie ma co zaczynać, a ktoś kto już tą wiedzę posiada może swobodnie przeanalizować gotowe po instalacji przykłady.

Tworzenie modelu

Najwyższy czas na danie główne - model. Jak już wielokrotnie wspominałem z punktu widzenia danych oraz informacji, model jest najważniejszym elementem każdego projektu napisanego w Kohanie, ale również w innych frameworkach. Właściwie to ON jest aplikacją, ponieważ to w nim znajduje się cała logika aplikacji, obliczenia, pobieranie danych - słowem wszystko to bez czego moglibyśmy stworzyć najwyżej prostą, statyczną stronę.

Model tak samo jak kontroler jest zwykłą klasą, możemy z niego dziedziczyć, tworzyć rozszerzone wersje modeli i tak dalej. Opisywanie tego drugi raz nie ma sensu, ponieważ sposób działania jest ten sam. Niestety w przypadku modeli tak jak jest to z kontrolerami i widokami, nie możemy tworzyć podkatalogów (a przynajmniej ja nie znalazłem na to sposobu). Trochę to moim zdaniem nieprzemyślane rozwiązanie, ale nic na to nie poradzimy. Przynajmniej w ramach ułatwienia życia developerom, Kohana w domyślnej klasie modelu tworzy instancję klasy Database w $this->db, dzięki czemu nie musimy o tym sami pamiętać.

Cenna uwaga jeśli chodzi o model - starajmy się go używać tylko do jednej rzeczy. Kuszące może się wydawać wrzucenie kilkudziesięciu metod obsługujacych różne części strony w jeden model (odpowiednik functions.inc.php czy innego potworka znanego ze starych skryptów i jeszcze starszych kursów), ale to naprawdę nie jest rozwiązanie. Jeśli musimy wykonać operacje, które nie dotyczą logiki aplikacji, a jedynie mają usprawniać pracę nad projektem (np. generowanie linków, generowanie tabelek), znacznie lepiej jest wykorzystać do tego helper albo napisać bibliotekę. Zaśmiecanie kodu jest be.

Temat sposobu pisania modelu, nazywania metod i konwencji z tym związanych jest szeroki jak rzeka i wykracza nieco poza tą serię (PODSTAWY frameworka), ale jestem pewien, że napiszę na ten temat wpis albo dwa gdy poczuję się odpowiednio bezczelny, by zacząć się uważać za alfę i omegę w tym temacie. W międzyczasie polecam przejrzeć po prostu inne klasy zawarte w Kohanie albo wyrobić sobie własną konwencję, która z czasem będzie ewoluować w kierunku bardziej ustandaryzowanego rozwiązania.

Słowa końcowe

Wpis wyszedł nieco dłuższy objętościowo, a mniej bogato w treść niż początkowo planowałem, ale jeśli zacznę już następny temat, równie dobrze mogę zacząć pisać książkę. Gdybym był nauczycielem to w ramach zadania domowego kazałbym pomyszkować w domyślnym kontrolerze oraz widoku, a także modelach z modułu Auth (są dość wdzięczne w tej materii). Poza tym polecam korzystać z oficjalnej dokumentacji Kohany, ponieważ ten tekst powinen być traktowany jako suplement niż jej zastępstwo. Powodzenia w kodzeniu! :)