Strona główna
O mnie
Projekty
Publikacje
Linki
|
|
„klepy internetowe" Autor: Adam Major
|
Wersja do druku
|
Artykul został pierwotnie opublikowany w magazynie Software 2.0 3/2002 - "Programowanie dla Internetu". |
www.software.com.pl |
Aby pobrać z sesji informacje o identyfikatorach wszystkich produktach jakie klient ma
w koszyku i skonstruować na tej podstawie zapytanie do bazy danych możemy się posłużyć
następującą funkcją.
Zmienna $zap zawiera identyfikatory produktów z koszyka w formacie
id1,id2,id3... pozostałą informację o wybranych produktach uzyskamy wydając
zapytanie SQL:
select id_pr, name, price_n, price_b, vat from prod where id_pr in ($zap);
W tablicy $qt_tab mamy zapisaną ilość dla poszczególnych
identyfikatorów produktów.
Po zastosowaniu w/w funkcji uzupełnionej o brakujące pobranie stosownych danych z
bazy oraz formatowanie wyników zapytania możemy uzyskać taką stronę prezentującą
zawartość koszyka.
|
|
<?php
function list_cart()
{
global $HTTP_SESSION_VARS;
$stan = $HTTP_SESSION_VARS[cart]->show_cart();
if (!$stan)
{
$HTTP_SESSION_VARS[c_total] = 0;
info_cart_empty();
return 0;
}
while (list($key, $value) = each($stan))
{
if ($key > 0)
{
$zap .= $key . ',';
$qt_tab[$key] = $value;
}
}
$zap_size = strlen($zap);
if ($zap_size == 0)
{
info_cart_empty();
return 0;
}
if ($zap[$zap_size-1] == ',')
$zap = substr ($zap, 0, -1);
//...
}
?>
Listing 7. Pobieranie informacji o identyfikatorach produktów w koszyku.
|
Rysunek 2. Strona prezentująca zawartość koszyka.
Równie ważne co zaplanowanie bazy danych i sposobu realizacji koszyka jest
Struktura katalogów
Dobrze zaplanowana struktura katalogów pozwala na łatwe zarządzanie plikami (np.
zmianę wyglądu itp.) serwisu oraz ewentualną rozbudowę jego funkcjonalności.
W głównym katalogu serwisu utworzyłem katalogi: admin, który zawiera wszelkie
skrypty potrzebne do działania panelu administracyjnego; pict zawiera wszystkie
obrazki tworzące wygląd serwisu; tpl przeznaczony jest na szablony dokumentów
itp. Osoby odpowiedzialne za layout sklepu powinny mieć tylko dostęp do
katalogów pict i tpl. W katalogu img tworzone są dwa podkatalogi (mini i big)
przechowujące zdjęcia produktów, miniaturki umieszczane są w mini, a zdjęcia o
normalnej wielkości w big. Nazwy plików powstają na podstawie dwóch pól:
identyfikatora produktu oraz typu pliku, zawartego w polach mini_t i big_t.
Katalog conf przeznaczony jest na przechowywanie pliku(ów) konfiguracyjnych,
jeśli istnieje taka możliwość to powinien zostać umieszczony poza DOCUMET_ROOT
(katalogiem widocznym przez serwer WWW). Niestety wielu ISP nie daje takiej
możliwości, dlatego należy stosować pliki konfiguracyjne z rozszerzeniem PHP
(lub innym przetwarzanym przez PHP).
|
|
Rysunek 3. Schemat struktury plików.
|
Skoro wspomniałem już o plikach konfiguracyjnych rozwińmy, krótko to dość mało
ekscytujące zagadnienie.
Plik konfiguracyjny
Warto stworzyć jeden plik konfiguracyjny dla całego sklepu, a następnie go inkludować
w skryptach, które muszą skorzystać z danych w nim zawartych. Najważniejszą
zasadą tworzenia takiego pliku jest to aby był on możliwie małych rozmiarów i
zawierał tylko te informacje, które są przydatne w większości skryptów. Jeśli
mamy klika zmiennych konfiguracyjnych używanych w zaledwie w 1 lub 2 plikach warto
się pokusić o zrobienie dla nich osobnego pliku konfiguracyjnego.
Cóż więc plik konfiguracyjny powinien zawierać? Na pewno informacje potrzebne
do korzystania z bazy danych, ścieżkę do katalogu img, może login i hasło do FTP'a.
Osobiście preferuje używanie jako plików konfiguracyjnych zwykłych skryptów PHP
zawierających dane w formacie $nazwa_zmiennej = wartosc;
Można co prawda zastosować bardziej elegancki i łatwiejszy w użytkowaniu, rodzaj
konfigów, na wzór pliku php.ini. Niestety mimo udostępnienia specjalnej
(nieudokumentowanej) funkcji parse_ini_file() ten sposób jest trochę wolniejszy
od zwykłego inkludowania. A zmian w konfiguracji sklepu nie dokonuje się w końcu
tak często.
|
|
<?
$db["host"] = 'localhost';
$db["base"] = 'nazwa_bazy';
$db["user"] = 'uzytkownik';
$db["pass"] = 'tajne_haslo';
$dir_img = '/home/httpd/html/sklep/img';
?>
Listing 8. Przykład pliku konfiguracyjnego cfg.php
|
Bezpieczeńśtwo
W tej części artykułu postaram się przedstawić najczęściej spotykane luki w
skryptach tego typu oraz sposoby zabezpieczania aplikacji przed złośliwymi
(nieuczciwymi) użytkownikami.
Tablice $HTTP_*_VARS
Bardzo zalecane jest stosowanie tablic asocjacyjnych z grupy $HTTP_*_VARS,
pozwalają one jednoznacznie stwierdzić skąd dane pochodzą. Od wersji 4.1.0 dostępne
są także tablice asocjacyjne (np. $_GET[]), które mają być zamiennikiem tych z
rodziny $HTTP_*_VARS. Ich dużą zaletą jest krótszy zapis oraz globalny zasięg
(w funkcjach nie trzeba ich deklarować za pomocą global $nazwa_zmiennej).
Część programistów PHP bagatelizuje problem, twierdząc, że skoro jest opcja
variables_order w pliku php.ini i zmienne sesji zawsze nadpisują zmienne
z innych źródeł (np. z GET) to praktycznie nie ma problemu. Przy źle napisanej
aplikacji stosowanie globalnej przestrzeni nazewniczej może doprowadzić do poważnej
luki w bezpieczeństwie.
Aby skutecznie się podszyć pod zmienną sesji trzeba odgadnąć nazwę zmiennej i
wartość jaką powinna przyjąć, jednak teoretycznie jest to możliwe. Dużo bardziej
widoczny jest ten problem w przypadku formularzy, bardzo łatwo (jeśli nie
sprawdzamy skąd zmienne pochodzą) jest udawać za pomocą parametrów URL'a zmienne,
które powinny być wysłane metodą POST.
|
|
// plik1.php
<?php
// jeśli poniższy blok się nie wykona
if ($cos == $costam)
{
session_register(zalogowany, zm2);
$zalogowany = 1;
// ...
}
?>
// plik2.php wywołany z parametrem zalogowany=1
<?php
session_start();
if ($zalogowany == 1) echo 'jesteś zalogowany';
?>
Listing 9. Podszycie się pod zmienną sesyjną za pomocą GET.
|
Przesyłanie zdjęć na serwer
Z tym zagadnieniem wiążą się dwa problemy. Pierwszy polega na sprawdzeniu czy
pliki spełniają kryteria postawione przed aplikacją, a drugi na odpowiednim
zabezpieczeniu katalogów w których pliki mają być przechowywane.
W przypadku uploadu plików metodą POST otrzymujemy tablicę $HTTP_POST_FILES
w której mamy informacje o nazwie tymczasowej pliku (tmp_name), rozmiarze pliku
(size) i oryginalnej nazwie pliku (name). Jest także udostępniana zmienna type
zawierająca ustawiony przez przeglądarkę typ mime pliku np. image/gif.
Jeśli dopuszczamy tylko przesyłanie zdjęć warto dodatkowo sprawdzić typ pliku
za pomocą funkcji GetImageSize() np.
$size = GetImageSize($HTTP_POST_FILES[plik][tmp_name]);
Odpowiednie zabezpieczenie katalogów przeznaczonych na zdjęcia jest bardziej złożone.
Najlepszym rozwiązaniem, wymagającym niestety własnego serwera lub bardzo
dobrych stosunków z administratorem naszego ISP jest stworzenie osobnego konta FTP
pozwalającego tylko i wyłącznie operować w katalogu img.
W takim przypadku po przesłaniu pliku via formularz WWW na serwer, za pomocą
funkcji FTP pobieramy plik z katalogu tymczasowego i umieszczamy w stosownym
podkatalogu img. Dzięki czemu katalogom img/mini i img/big
możemy nadać prawa dostępu 705.
Minusem tego sposobu jest to że wszelkie operacje na plikach zdjęć musimy
wykonywać za pomocą FTP.
Drugim z ciekawszych rozwiązań jest ustawienie przez administratora serwera
odpowiednich grup i praw dostępu do w/w katalogów. Na przykład, serwer WWW chodzi
na prawach użytkownika apache, nasz użytkownik to zdzichu.
Prosimy więc administratora aby ustawił dla katalogów mini/ i big/ grupę
apache, właściciela zdzichu i prawa dostępu 775.
Trzecim rozwiązaniem, które możemy zrealizować praktycznie na każdym serwerze
bez pomocy administratora jest przechowywanie zdjęć w katalogu o bardzo długiej
i trudnej nazwie, umieszczonego najlepiej ponad DOCUMENT_ROOT.
Pobieranie zdjęć odbywa się wtedy za pomocą skryptu ustawiającego odpowiednie
nagłówki, odczytującego porcjami plik z sekretnego miejsca i wysyłającego te
dane do przeglądarki. Niestety takie rozwiązanie obciąża znacznie serwer.
Istnieją także inne możliwości, np. włączenie trybu bezpiecznego (safe mode)
w PHP, niestety mechanizm ten mimo bardzo dobrych założeń nie do końca spełnia
swoje zadanie. Innym rozwiązaniem stosowanym niestety bardzo rzadko
(z powodu trudności w odpowiednim skonfigurowaniu) przez ISP jest uruchamianie
serwera WWW z prawami właściciela wirtualnego serwera (virtual host), w takim
przypadku wystarczą prawa 700.
Przechowywanie zakodowanych haseł
Wszelkie dane poufne takie jak np. hasła administratorów powinny być przechowywane
w bazie danych w formie zaszyfrowanej. Można do tego wykorzystać standardową
funkcję crypt() lub gdy potrzebujemy lepszego zabezpieczenia użyć funkcji z
modułu mcrypt.
Podczas logowania wprowadzone hasło z formularza powinno być zakodowane następnie
w skrypcie porównane z hasłem pobranym z bazy danych. Jeśli tylko istnieje taka
możliwość to należy stosować bezpieczne protokoły sieciowe takie jak SSL do
przesyłania formularza zamówieniowego i logowania.
Weryfikacja danych z formularza
Bardzo istotnym elementem zabezpieczenia się przed złośliwymi "klientami" jest
skrupulatna weryfikacja danych pobranych z formularza zamówieniowego. Należy
ją wykonywać po stronie serwera, wszelkie sprawdzanie client-side np. za pomocą
JavaScript nie mają wielkiego znaczenia, ze względu na łatwość obejścia takich
zabezpieczeń. Sprawdzać trzeba każde pole formularza, które jest wymagane do
zawarcia transakcji. Kryteria, które zastosujemy powinny być dość surowe np.
imię może zawierać tylko litery i musi mieć minimum 3 znaki.
Do tego typu weryfikacji niezbędne są wyrażenia regularne, za pomocą których
możemy szybko, łatwo i przyjemnie sprawdzać nawet bardzo złożone kryteria.
Niekiedy warto wprowadzić obowiązek wypełnienia takich unikalnych danych jak NIP
lub PESEL, pozwoli nam to zastosować algorytmy do sprawdzania poprawności tych
danych i na starcie już odstraszyć wielu potencjalnych kawalarzy.
Zachęcam do stosowania licznika błędnych wypełnień formularza, zliczających
wszystkie kolejne próby wysłania błędnie wypełnionego formularza. Realizacja
takiego licznika jest bardzo prosta, po zatwierdzeniu formularza następuje
weryfikacja danych jeśli wystąpił jakikolwiek błąd to zwiększamy licznik błędów
i zapisujemy do sesji (jeśli wcześniej nie istniał w sesji to wpisujemy 1).
Następnie zwracamy użytkownikowi do poprawki formularz z wpisanymi przez niego
danymi oraz zaznaczonymi błędami. Po ponownym wysłaniu formularza procedura się
powtarza.
W panelu administracyjnym obok danych zamówienia prezentujemy wskazanie licznika,
na podstawie tych danych opiekun sklepu może podjąć decyzję czy warto ryzykować
przy realizacji takiego zamówienia.
|
|
<?php
function check_pesel($pesel)
{
global $pesel_sex;
if (strlen($pesel) != 11 || !is_numeric($pesel))
return 0;
if (($pesel[9] % 2) == 0)
$pesel_sex = ' kobieta';
else $pesel_sex = 'mężczyzna';
$steps = array(1, 3, 7, 9, 1, 3, 7, 9, 1, 3);
for ($x = 0; $x < 10; $x++)
$sum_nb += $steps[$x] * $pesel[$x];
$sum_m = 10 - $sum_nb % 10;
if ($sum_m == 10) $sum_m = 0;
if ($sum_m == $pesel[10]) return 1;
return 0;
}
?>
Listing 10. Funkcja sprawdzająca poprawność numeru PESEL i zwracająca płeć właściciela. |
< część 1 | część 2
sklepy_lst.zip - Wszystkie listingi zawarte w artykule (4 KB).
Artykul został pierwotnie opublikowany w magazynie Software 2.0 3/2002 - "Programowanie dla Internetu". |
www.software.com.pl |
|