% $Id: implementaceweb.tex,v 1.1 2007/01/18 22:20:30 jarda Exp $

\subsection{Úvod}

Webové rozhraní databáze benchmarků řeší několik záladních požadavků.
Snažil jsem se zajistit, aby aplikace poskytovala alespoň minimální potřebnou
bezpečnost, aby byla schopná pracovat nad různými databázovými stroji bez
nutnosti rozsáhlých změn a aby její kód byl přehledný a snadno udržovatelný.
Následující text bude tedy popisovat nejenom základní funkčnost aplikace,
ale i aspekty bezpečnostní, techniky přístupu k databázi a základní
strukturu, na níž je aplikace postavena.


\subsection{Zachování důvěrnosti informací}

Existuje několik způsobů, jak ve webové aplikaci zajistit tyento požadavek. Mezi
způsoby, které naprosto nevyhovují z hlediska bezpečnosti a flexibility, patří
například omezení přístupu na pevně danou IP adresu a sledování odkazující stránky.
Zvažoval jsem dva způsoby kontroly oprávnění:

{\odstavec

Klasickým řešením je použít tzv. HTTP autentizaci, která využívá schopnosti HTTP
serveru povolit přístup k vybraným zdrojům pouze některým uživatelům (soubor .htaccess).
Tento způsob má mnohé nevýhody:
}

\begin{itemize}

\item
Nutnost přímé editace souborů na serveru.

\item
Systém oprávnění je spjat se souborovou strukturou na serveru.

\item
Změny v aplikaci vyžadují často změny v souboru .htaccess - snadná možnost
zanesení chyby.
        
\end{itemize}

Řešení, které jsem zvolil já, využívá uživatelských účtů uložených v databázi, které
mohou mít tři úrovně oprávnění. Dále je možné v konfiguraci přepnout možnost prohlížení
a stahování benchmarků pouze pro určité osoby nebo pro všechny návštěvníky bez
nutnosti přihlášení.


\subsection{Přístup aplikace k databázi}

Pro přístup k databázi byla zvolena knihovna AdoDB. Důvodu pro její zvolení byly:

\begin{itemize}

\item
Podpora mnoha volně dostupných i komerčních databázových systémů.
        
\item
Vysoká rychlost.

\item
Vysoká úroveň abstrakce a zároveň podpora potřebných vlastností databáze.

\item
Možnost nezávislé slovníkové definice databázové struktury.

\item
Existence této knihovny i pro jazyk Python, což zajišťuje jednotný styl přístupu k datům
ve webové aplikaci i pomocném řádkovém programu.
        
\end{itemize}

Tenkou vrstvu rozhraní ke knihovně AdoDB představuje soubor database.inc.php, který
zajišťuje připojení k databázi na základě konfiguračního souboru.

\subsection{Souborová struktura aplikace}

Kód aplikace je tvořen soubory v jazyce PHP. Zobrazované soubory mají název\textit{zaklad.php},
pomocné soubory vkládané do jiných souborů mají název \textit{zaklad.inc.php}. Indexový soubor
se nazývá \textit{index.php}, ostatní zobrazované soubory mají, až na výjimky, názvy tvořené
podle šablony \textit{typdat\_TYPAKCE.php} - například benchmark\_ADD.php. Použité názvy akcí jsou:

\begin{itemize}

\item
EDIT - stránka s formulářem pro editaci dat

\item
CHANGE - stránka, která provede změnu v databázi podle údajlů z formuláře EDIT akce

\item
NEW - stránka s formulářem pro vytvoření nových dat

\item
ADD - stránka, která vloží data zadaná formulářem ve stránce NEW

\item
SHOW - zobrazení dat

\item
SEARCH - vyhledání dat

\item
DOWNLOAD - stažení souboru

\item
LISTING - zobrazení výpisu

\item
DELETE - vymazání dat
    
\end{itemize}

\subsection{Základní uživatelské rozhraní}

Uživatelské rozhraní aplikace je dáno dvousloupcovým plovoucím designem
založeným na kaskádových stylech (CSS). V levé části stránky se nachází menu, v
pravé části samotná zobrazovaná data nebo zadávané hodnoty.

\begin{figure}[h]
\includegraphics[width=150mm]{design}
\caption{Rozvržení stránky}
\label{fig:design}
\end{figure}


\subsection{Objektová struktura aplikace}

Základním kamenem objektové hierarchie je třída Page. Instance některé z podtříd
této třídy je obsažena na každé zobrazené stránce.

\begin{figure}[h]
\includegraphics[width=150mm]{PHP_classes}
\caption{Hierarchie tříd stránek}
\label{fig:PHP_classes}
\end{figure}

\subsubsection{Třída Page}

Třída Page definuje metody, které jsou používány konkrétními potomky této třídy. Jedná se o
metody, které umožňují zjistit, zda má aktuální uživatel právo provést akci určitého typu,
dokáže vytvořit standardní HTML hlavičku včetně kódování, kaskádového stylu a podobně. Ve
třídě Page je také definována statická proměnná \${}allow\_view\_for\_anyone, která určuje,
zda právo prohlížení a stahování benchmarků mají i nepřihlášení uživatelé.

\subsubsection{Metoda run() a funkce launchPage()}

Metoda run() spustí standardní proces tvorby stránky s využitím metod definovaných v podtřídách
třídy Page(). Kód metody je následující:

\begin{verbatim}
function run()
{
    $this->initSession();
    $this->ensureUserHasNecessaryPermission();
    $this->customInitialization();
    if (!$this->checkEnvironment())
        $this->handleInvalidEnvironment();
    else
        $this->createPage();
}
\end{verbatim}

{\odstavec

Význam kódu je následující: Nejprve se inicializuje relace, která překlenuje nestavovost
HTTP protokolu a zajišťuje kontinuitu identifikace přihlášeného uživatele. Po identifikaci
se metoda ujistí, že uživatel má potřebná práva - má potřebnou úroveň oprávnění podle
metody getRequiredAccessLevel(); v případě selhání se vypíše chybová hláška a zpracování
končí. Poté dostanou podtřídy možnost provést vlastní inicializaci, například
předpřipravit nějaká pomocná pole. Poté podtřída může otestovat, zda je prostředí v pořádku
- např. jestli jsou připravena potřebná data v superglobálních proměnných \${}\_GET či
\${}\_POST. Pokud se zjistí nedostatek, proběhne jeho ošetření, jinak dostane podtřída
za úkol vytvořit samotnou stránku.


Funkce launchPage() zajistí vytvoření instance třídy Page() a spuštění její metody run():
}

\begin{verbatim}
function launchPage()
{
    $pageObject = new PageClass();
    $pageObject->run();
}
\end{verbatim}

\subsubsection{Struktura typické stránky}

Typická stránka se skládá ze tří částí - importu souboru definujícího předka třídy stránky,
vytvoření třídy stránky a zavolání funkce launchPage():

\begin{verbatim}
<?php 
    require_once('simplePage.inc.php'); 

    class PageClass extends EditDBSimplePage
    {
        # Zde se nachází implementace konkrétních metod
    }

    launchPage();
?>
\end{verbatim}

Na rozdíl od ranné verze webového rozhranní tento jednotný styl zajišťuje
velmi dobrou přehlednost a srozumitelnost, což napomůže udržovatelnosti a rozšiřitelnosti
aplikace. Z kódu je možno vyčíst následující:

\begin{itemize}

\item Z volání funkce require\_once() je vidět, do které větve stránka patří (simple / structured).

\item Z deklarace předka třídy PageClass je navíc na první pohled zřejmé, jaká je potřeba
    úroveň oprávnění uživatele.

\item Volání funkce launchPage() zajistí jednotný proces zpracování stránky.

\item V kombinaci se znalostí funkce launchPage() můžeme říci, že třída stránky se musí vždy
    nazývat PageClass.
        
\end{itemize}


\subsubsection{Třída SimplePage a třída StructuredPage}

Jak je vidět z hierarchie tříd uvedené výše, jsou podtřídy třídy Page rozděleny do dvou větví:

\begin{itemize}

\item Třída SimplePage je určena k vytváření stránek, které jsou určeny zejména ke zpracování dat
a obvykle po zpracování provede přesměrování na některou jinou stránku podle kontextu dané akce.

\item Třída StructuredPage zobrazuje klasický design aplikace - hlavní menu a specifický obsah.
    
\end{itemize}

Podtřídy třídy SimplePage implementují metodu createPage(). Implementace obyčejně obsahuje práci
s databází a následné přesměrování na jinou stránku. Může však i zobrazit nějaký obsah; k vytvoření
základní hlavičky lze s výhodou použít metodu getHeader().

{\odstavec
Třída StructuredPage implementuje metodu createPage() tak, že zobrazí základní rozložení stránky
a kontextové menu a zavolá metodu printContents(), která vytvoří obsahovou část stránky.
}

\subsection{Řízení přístupu}

Třída Page() ve své metodě run() hlídá, zda připojený uživatel disponuje potřebnou úrovní
oprávnění. Jak je vidět z hierarchie tříd, názvy listových tříd hierarchického stromu mají předponu,
která udává, jakou úroveň oprávnění musí uživatel mít, aby dostal přístup na stránku vytvořenou
instancí dané třídy.

\subsubsection{Proces přihlášení uživatele}

Uživatel je při pokusu o přihlášení vyzván k zadání uživatelského jména a hesla.
Po jejich zadání je přihlášení provedeno následujícím kódem:

\begin{verbatim}
$db = connect();

$query = "SELECT * from bDBUser " .
         "WHERE login = '$_POST[login]' " .
         "AND password = '$_POST[password]'";

$recordSet = $db->Execute($query);

$row = $recordSet->FetchRow();

if ($row) {
    session_name('benchmark');
    session_start();
    session_regenerate_id();

    # remove old records from session user table {{{
    $date = date("Y-m-d H:i:s", strtotime('-2 days'));

    $db->Execute("DELETE FROM sessionUser WHERE createdTime < '$date'");
    # }}}

    # add a new a new record to user session table {{{
    $date = date("Y-m-d H:i:s");

    $sessionId = session_id();

    $query = "INSERT INTO sessionUser (sessionId, userId, createdTime) " .
             "VALUES ('$sessionId', $row[id], '$date')";

    $db->Execute($query);
    # }}}
    
}
\end{verbatim}

Napřed je provedeno vyhledání záznamu o uživateli v příslušné tabulce.
Pokud je uživatel s daným uživatelským jménem a heslem nalezen, vytvoří
se nová relace, vymažou se staré páry ID relace : ID uživatele (proces
slouží i pro čištění starých záznamů z tabulky) a do tabulky se vloží nový
pár. Od této chvíle je relace spojena s přihlášeným uživatelem.

\subsubsection{Identifikace uživatele a zjištění jeho vlastností}

Jak jsem uvedl výše, relace zajišťuje kontinuální identifikaci uživatele. Pomocí relace lze
získat informace o uživateli včetně přístupových oprávnění. Tyto informace jsou získány
pomocí metody getUserInfo():

\begin{verbatim}
function getUserInfo()
{
    $db = connect();
    
    $sessionId = session_id();

    $query = "SELECT bDbUser.id AS userId, "
             "bDBUser.canEditDatabase AS canEditDatabase, " .
             "bDBUser.canChangeOwnPassword AS canChangeOwnPassword, " .
             "bDBUser.canEditUserAccounts AS canEditUserAccounts, " .
             "bDBUser.password AS password " .
             "FROM bDBUser, sessionUser " .
             "WHERE sessionUser.sessionId = '$sessionId' AND " .
             "bdbUser.id = sessionUser.userId";
             
    $recordSet = $db->Execute($query);

    return $recordSet->FetchRow();
}
\end{verbatim}

\subsubsection{Proces odhlášení uživatele}

Odhlášení uživatele spočívá ve zrušení vazby jeho relace k uživatelskému účtu.
Následující třída pochází ze souboru logout.php a demonstruje zároveň 
jednoduché použití podtřídy SimplePage:

\begin{verbatim}
class PageClass extends ViewSimplePage
{
    function createPage()
    {
        $sessionId = session_id();

        $db = connect();

        $db->Execute("DELETE FROM sessionUser WHERE sessionId = '$sessionId'");

        session_regenerate_id();

        header("Location: index.php"); /* Redirect browser */ 
    }
}
\end{verbatim}

Kód je jednoduchý: Z databáze je vymazáno párování ID relace a ID uživatele
a poté je ještě ID relace změněno. Po odhlášení je prohlížeč uživatele přesměrován
na hlavní stránku.

\clearpage
\subsubsection{Kontextově závislá nabídka operací}

Hlavní nabídka neboli menu je závislá na úrovni oprávnění uživatele. Pro
demonstraci ukážu menu uživatele, jenž má právo pouze prohlížet obsah databáze:

\begin{figure}[h]
\begin{center}
\includegraphics[width=70mm]{basicMenu}
\caption{Základní nabídka operací}
\label{fig:basicMenu}
\end{center}
\end{figure}

a nabídka uživatele s maximálním oprávněním:

\begin{figure}[h]
\begin{center}
\includegraphics[width=70mm]{maxMenu}
\caption{Maximální nabídka operací}
\label{fig:maxMenu}
\end{center}
\end{figure}

\subsection{Implementované funkce aplikace}

Webové rozhraní je poměrně rozsáhlé a nemá smysl podrobně popisovat všechny funkce.
Zaměřím se pouze na nejzajímavější použité techniky a krátce popíši základní
implementované operace.


\subsubsection{Vytvoření tabulek}

Vstupem na stránku createTables.php je vytvořena databázová struktura -
všechny potřebné tabulky.  Tato stránka není chráněna heslem, neboť její
opakované spuštění nezpůsobí žádné škody - tabulky jsou již vytvořeny a proto
je akce ignorována.

{\odstavec
Tabulky se vytvářejí pomocí knihovny AdoDB s využitím tzv. slovníkové definice.
Ta umožňuje co největší přenositelnost definice při zachování co nejširších
vyjadřovacích prostředků. Dokonce dokáže emulovat některé vlastnosti například
pomocí triggerů. Příkladem je vytvoření autoinkrementačních sloupců.

Slovníkové definice lze sestavit dvěma způsoby; já jsem použil a demonstrovat zde
budu, novější, deklarativní styl. Na následujícím příkladu předvedu definici tabulky
v AdoDb s použitím slovníkové definice:
}

\begin{verbatim}
$tableName = 'benchmarkInstance';

$fields = "
    id I AUTO KEY,
    benchmarkId I CONSTRAINTS 'FOREIGN KEY REFERENCES benchmark',
    benchmarkSetId I CONSTRAINTS 'FOREIGN KEY REFERENCES benchmarkSet',
    formatId I CONSTRAINTS 'FOREIGN KEY REFERENCES format',
    description C(2000),
    fileName C(100)
";

createTable($tableName, $fields);
\end{verbatim}

Nadeklaroval jsem název tabulky, seznam sloupců tabulky a poté jsem zavolal funkci createTable(),
která tabulku vytvoří. Jak vidíme, deklarace sloupce se skládá z názvu, definice typu s případným
zadáním rozměrů sloupce a definice doplňkových vlastností. Například sloupec id jsem nadefinoval
jako primární klíč, jehož hodnota je automaticky zvyšována o jedničku (autoincrement). Tři
následující sloupce mají definován cizí klíč pro zachování integrity dat. Použité datové typy
jsou INTEGER a VARCHAR.

{\odstavec
Funkce pro vytvoření tabulky:
}

\begin{verbatim}
function createTable($tableName, $fields, $tabOptArray=false)
{
    $db = connect();
    $dict = NewDataDictionary($db);
    $sqlArray = $dict->CreateTableSQL($tableName, $fields, $tabOptArray);
    $result = $dict->ExecuteSQLArray($sqlArray);
}
\end{verbatim}

Pár poznámek k funkci - po připojení k databázi se ze slovníku vytvoří pole SQL příkazů.
Toto pole příkazů se provede metodou ExecuteSQLArray(). Existují však i další možnosti
využití: vygenerovat textový soubor s SQL příkazy k dalšímu zpracování nebo automaticky převést
na kód využívající jinou knihovnu pro práci s databází. Tímto převodem se však ztrácí
možnost nasazení na široké škále databázových systémů bez dalších úprav.


\subsubsection{Inicializace tabulek}

Pokud není vytvořen žádný uživatelský účet, je vytvořen první účet s administrátorskými
právy. Poté jsou tabulky naplněny základními daty. Mezi tato základní data patří
zejména podporované formáty, atributy těchto formátů a základní benchmarkové sady.


\subsubsection{Přihlášení, odhlášení, změna hesla}

Nepřihlášený uživatel se může přihlásit uživatelským jménem a heslem, přihlášený
uživatel se může odhlásit. Pokud má uživatel oprávnění, může změnit vlastní heslo.


\subsubsection{Administrace uživatelů}

Uživatel s oprávněním k editaci uživatelských účtů může přidávat a rušit
uživatelské účty, může měnit oprávnění jednotlivých uživatelů


\subsubsection{Práce s formáty}

Formáty lze zobrazovat a editovat jejich popis a podporované atributy.


\subsubsection{Práce s benchmarkovými sadami}

Je možné měnit popis sady, zobrazovat informace o sadě a vyhledávat instance.


\subsubsection{Práce se sadami benchmarků}

Uživatel může vytvářet a vyhledávat instance benchmarků. Všechny nalezené benchmarky
pro daná kritéria je možno stáhnou ve vytvořeném souboru formátu ZIP nebo si stáhnout
pouze zvolený benchmark.


% vim:enc=iso8859-2:fenc=iso8859-2:
