CakePHP project építés 6. rész

cakephpKis alkalmazásunk biztonsági rétegét az előző részben fejlesztgettük, de van még egy lépés amit biztosan alkalmazni fogunk. Jelen pillanatban bárki tud termékeket hozzáadni, módosítani és törölni aki meglátogatja az oldalunkat. Ez nem feltétlenül az amit mi szeretnénk.

A user modul

Meg szeretnénk oldani, hogy csak az oldal adminisztrátora által létrehozott user(ek) érhessék el az új termékek felvitelére, szerkesztésére és törlésére szolgáló részeket.

Sql

Hozzuk létre a users táblát a következők szerint, és adjunk hozzá egy admin nevű usert goranga jelszóval. A jelszo mezőt az sql md5 függvényével titkosítjuk.

CREATE TABLE `users` (
  `id` tinyint(3) unsigned NOT NULL auto_increment,
  `nev` varchar(10) NOT NULL,
  `jelszo` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
);


INSERT INTO `users` (`nev`, `jelszo`) VALUES 
('admin', MD5('goranga'));

A user model

Porolgassul le a bake scriptet és hozzuk létre vele a modelt.

rrd:~/Sites/tulasi/cake/scripts rrd$ php bake_hu.php 

 ___  __  _  _  ___  __  _  _  __      __   __  _  _  ___ 
|    |__| |_/  |__  |__] |__| |__]    |__] |__| |_/  |__ 
|___ |  | | \_ |___ |    |  | |       |__] |  | | \_ |___ 
---------------------------------------------------------------


Bake -app in /Users/rrd/Sites/tulasi/app (y/n) 
[y] > n

What is the full path for this app including the app directory name?
Example: /Users/rrd/Sites/tulasi/myapp  
[/Users/rrd/Sites/tulasi/myapp] > /Users/rrd/Sites/tulasi/

Bake -app in /Users/rrd/Sites/tulasi/ (y/n) 
[y] > 


Baking...
---------------------------------------------------------------
Name: app
Path: /Users/rrd/Sites/tulasi/app
---------------------------------------------------------------
[M]odel
[C]ontroller
[V]iew

What would you like to Bake? (M/V/C) 
> m
---------------------------------------------------------------
Model Bake:
---------------------------------------------------------------
Possible Models based on your current database:
1. Termek
2. Termekcsoport
3. User

Enter a number from the list above, or type in the name of another model.  
> 3

Would you like to supply validation criteria for the fields in your model? (y/n) 
[y] > 


Name: id
Type: integer
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1- VALID_NOT_EMPTY
2- VALID_EMAIL
3- VALID_NUMBER
4- VALID_YEAR
5- Do not do any validation on this field.

... or enter in a valid regex validation string.

  
[5] > 


Name: nev
Type: string
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1- VALID_NOT_EMPTY
2- VALID_EMAIL
3- VALID_NUMBER
4- VALID_YEAR
5- Do not do any validation on this field.

... or enter in a valid regex validation string.

  
[1] > 


Name: jelszo
Type: string
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1- VALID_NOT_EMPTY
2- VALID_EMAIL
3- VALID_NUMBER
4- VALID_YEAR
5- Do not do any validation on this field.

... or enter in a valid regex validation string.

  
[1] > 

Would you like to define model associations (hasMany, hasOne, belongsTo, etc.)? (y/n) 
[y] > n

---------------------------------------------------------------
The following model will be created:
---------------------------------------------------------------
Model Name:    User
DB Connection: default
DB Table:   users
Validation:    Array
(
    [nev] => VALID_NOT_EMPTY
    [jelszo] => VALID_NOT_EMPTY
)

---------------------------------------------------------------

Look okay? (y/n) 
[y] > 

Creating file /Users/rrd/Sites/tulasi/app/models/user.php
Wrote/Users/rrd/Sites/tulasi/app/models/user.php

Cake test suite not installed.  Do you want to bake unit test files anyway? (y/n) 
[y] > n

Magyarázat nincs, aki nem érti, hogy mit miért az olvassa el újra az előző részeket. 😛

A users controller

rrd:~/Sites/tulasi/cake/scripts rrd$ php bake_hu.php 

 ___  __  _  _  ___  __  _  _  __      __   __  _  _  ___ 
|    |__| |_/  |__  |__] |__| |__]    |__] |__| |_/  |__ 
|___ |  | | \_ |___ |    |  | |       |__] |  | | \_ |___ 
---------------------------------------------------------------


Bake -app in /Users/rrd/Sites/tulasi/app (y/n) 
[y] > n

What is the full path for this app including the app directory name?
Example: /Users/rrd/Sites/tulasi/myapp  
[/Users/rrd/Sites/tulasi/myapp] > /Users/rrd/Sites/tulasi/

Bake -app in /Users/rrd/Sites/tulasi/ (y/n) 
[y] > 


Baking...
---------------------------------------------------------------
Name: app
Path: /Users/rrd/Sites/tulasi/app
---------------------------------------------------------------
[M]odel
[C]ontroller
[V]iew

What would you like to Bake? (M/V/C) 
> c
---------------------------------------------------------------
Controller Bake:
---------------------------------------------------------------
Possible Controllers based on your current database:
1. Termekek
2. Termekcsoportok
3. Users

Enter a number from the list above, or type in the name of another controller.  
> 3

Would you like bake to build your controller interactively?
Warning: Choosing no will overwrite  controller if it exist. (y/n) 
[y] > 

Would you like to use scaffolding? (y/n) 
[y] > n

Would you like to include some basic class methods (index(), hozzaad(), mutat(), szerkeszt())? (y/n) 
[n] > 

Nem akarunk scaffoldinget használni, mert jelenleg nem akarunk usereket kezelő admin felületet létrehozni. Alkalmazásunknak 1 vagy 2 usere lesz, ezek adataink kezelését közvetlenül sql-ből fogjuk megoldani. Így szükségtelen (sőt inkább nem biztonságos) létrehozni a hozzaad, szerkeszt, töröl, stb funkciókat.

Would you like this controller to use other models besides 'User'? (y/n) 
[n] > 

Would you like this controller to use other helpers besides HtmlHelper and FormHelper? (y/n) 
[n] > 

Would you like this controller to use any components? (y/n) 
[n] > 

Would you like to use Sessions? (y/n) 
[y] > 

---------------------------------------------------------------
The following controller will be created:
---------------------------------------------------------------
Controller Name:        Users
---------------------------------------------------------------

Look okay? (y/n) 
[y] > 

Creating file /Users/rrd/Sites/tulasi/app/controllers/users_controller.php
Wrote/Users/rrd/Sites/tulasi/app/controllers/users_controller.php

Cake test suite not installed.  Do you want to bake unit test files anyway? (y/n) 
[y] > n

CakePHP session kezelés

Annak ellenőrzésére, hogy a szóbanforgó látogatónak van-e joga elérni a termék és termékcsoport adminisztrációs részét session ellenőrzést fogunk használni.

A Cake 3 féleképpen tudja sessionjainkat tárolni.

  1. a normál php beállítás szerint fileokban, általában a /tmp könyvtárban
  2. a /app/tmp/sessions könyvtárban szintén fileokban
  3. adatbázisban

Mindhárom megfelelő, alkalmazások szerint külön-külön dönthetjük el, hogy melyiket használjuk. Mivel a fileokban tárolt sessionok sima szövegként tárolódnak el nem ajánlatos jelszavakat, vagy más érzékeny adatokat tárolni, vagy legalábbis titkosítás nélkül tárolni bennük. A három közül az adatbázisos tárolás a legbiztonságosabb, de ez minden lap letöltésekor végrehajt két sql lekérést, ami egy nagy forgalommal bíró oldalnál nem feltétlenül célszerű. Mivel a mi oldalunkat egyszerre nem fogja sok-sok user használni (mivel eleve csak egy van), nyugodtan választhatjuk az adatbázisos tárolást. Ehhez a /app/config/core.php define('CAKE_SESSION_SAVE', 'php'); sorát cseréljük le define('CAKE_SESSION_SAVE', 'database');-re. Ezzel megadtuk, hogy a Cake adatbázis táblákban tárolja a sessiont. Ehhez jó lesz létrehozni az AB táblát is amibe majd belekerülnek a session adatok. A tábla felépítését nem kell kitalálnunk, a /app/config/sql/sessions.sql fileban megtalálhatjuk.

CREATE TABLE cake_sessions (
  id varchar(255) NOT NULL default '',
  data text,
  expires int(11) default NULL,
  PRIMARY KEY  (id)
);

A session kezelést előkészítettük, léphetünk tovább.

User be és kiléptetés: view

Egy formra lesz szükségünk, ahol a user meg tudja adni a nevét és a jelszavát. Hozzuk létre a /app/views/users/belep.thml filet az alábbi tartalommal.

<?php
if ($error){
  print '<p>Nem sikerült bejelentkezned, próbáld meg még egyszer!</p>';
  }
?>

<form action="<?php print $html->url('/users/belep'); ?>" method="post">
<div>
  <label for="nev">Név:</label>
  <?php print $html->input('User/nev', array('size' => 20)); ?>
</div>
<div>
  <label for="jelszo">Jelszó:</label>
  <?php print $html->password('User/jelszo', array('size' => 20)); ?>
</div>
<div>
  <?php print $html->submit('Belépés'); ?>
</div>
</form>

A kód magáért beszél, ha lesz hibaüzenetünk, akkor jelenítsük meg, és legyen egy formunk a megfeleő input mezőkkel. A formban a $html helpert használjuk, úgy hogy Modelnév/mezőnév párossal adjuk meg, hogy melyik input mire való.

Megjegyzés: ha valakinek itt az oldal rossz karakterkódolással jelenik meg, ne aggódjon, hamarosan orvosolni fogjuk ezt a problémát is.

User be és kiléptetés: controller

A kontrollerünk nem is nagyon fog mást csinálni, mint a beléptető form adatait fogja feldolgozni.
Hozzuk létre a /app/controllers/users_controller.php filet az alábbi módon.

<?php
class UsersController extends AppController {
  var $name = 'Users';

Eddig remélem világos 🙂

 
  function belep(){    //bejelentkezés
    //Nem kellenek hibaüzenetek, egy hacker sok infót kiszedhet belőle
    $this->set('error', false);
    //Ha a user kitöltötte a bejelentkező formot
    if (!empty($this->data)){
      //ellenőrizzük le, hogy a usernév szerepel-e az AB-ban
      $valaki = $this->User->findByNev($this->data['User']['nev']);

Itt érdemes megjegyezni a findByMezőnév függvényt. Ez a Cake egy újabb aranyos kis segédfunkciója. Ha szükségünk van egy mező szerinti lekérdezésre egy controllerben akkor a findByMezőnév függvény jó segítséget ad a számunkra. ahelyett, hogy SQL utasítást és where szűrést kellene csinálnunk egyszerűen meghívhatjuk a finByMezőnév függvényt a Mezőnév helyére az adattáblában létező bármilyen mezőnevet beírva.

      if(!empty($valaki['User']['jelszo']) && $valaki['User']['jelszo'] == md5($this->data['User']['jelszo'])){
        //létezik ilyen nevű user és a megadott jelszó egyezik az ABbelivel

Mivel az AB-ban md5-tel titkosítottuk a jelszót a user által beírt jelszóra is le kell generálnunk az md5-t, hogy összehasonlíthatóak legyenek.

        $this->Session->write('User', $valaki['User']['nev']);
        //küldjük el a termék kezelő oldalra
        $this->redirect('/termekek/');
        }

Ha a user sikeresen megadta a nevét és jelszavát, akkor kiírjuk a sessionba a nevét, és elküldjük valamelyik oldalra. Ha több adatot akarunk a sessionba tárolni a Session->write() metódusával megtehetjük. Átadhattuk volna neki a teljes $valaki[‘User’] tömböt is, de ezzel a jelszó md5-je bekerülne a sessionba, amire egyrészt semmi szükségünk, másrészt meg egy biztonsági rés.

      else{    //nem stimmeltek a bejött adatok
        //állítsuk a hibát igazra
        $this->set('error', true);
        //a magam részéről itt loggolnám, hogy sikertelen bejelentkezési kísérlet történt
        }
      }
    }

Ha nem stimmelnek az adatok beállítjuk a $error változót és a user visszakerül a bejelentkező oldalra.

  function kilep(){  //kijelentkezés
    //töröljük a session adatokat
    $this->Session->delete('User');
    //és léptessük mondjuk a nyitóoldalra
    $this->redirect('/');
    }
}
?>

Amikor a user kijelentkezik, akkor töröljük a session adatokat.

Be van a user lépve?

A userünk most már be tud jelentkezni. A következő lépés az, hogy azoknál a controller funkcióknál ahol lényeges, hogy belépett vagy be nem lépett látogatóról van szó meg kell vizsgálnunk, hogy létezik-e a session, illetve, hogy milyen adatok vannak benne, és ennek függvényében döntjük el, hogy mit kell tenni. Ehhez létre kell hoznunk egy olyan függvényt ami a belépés tényét (jobban mondva esetünkben a User session változó létét) fogja ellenőrizni. Mivel ezt a függvényt több controllerből is el akarjuk érni a /app/app_controller.php fileba tesszük.

function userEllenorzo(){
  //ha nincs bejelentkezve még
  if (!$this->Session->check('User')){
    //küldjük a bejelentkező oldalra
    $this->redirect('/users/belep');
    exit();
    }
  }

A függvény egyszerű, ha nem létezik a User nevű session változó akkor a látogatót a beléptető oldalra irányítja, máskülönben igazat ad vissza.

Most már nem maradt más hátra, mint a kis ellenőrző függvényünket meghívni azokon a pontokon, ahol ellenőrizni akarjuk, hogy a user be van-e lépve. Ezek jelen pillantban a termékek hozzaad, szerkeszt és töröl metódusai.

A /app/controllers/termekek_controller.php fileban a hozzáad függvényt módosítsuk például így:

function hozzaad() {
  $this->userEllenorzo();
  if(empty($this->data)) {
  ...

Ennek ugye az lesz a hatása, hogy a hozzaad metódus hívásakor ha a user nincs bejelentkezve akkor a belépő oldalra kerül. Ezt ki is próbálhatjuk. Lépjünk ki a http://localhost/~rrd/tulasi/users/kilep link segítségével, majd hívjuk meg a http://localhost/~rrd/tulasi/termekek/hozzaad URL-t. Ha mindent jól csináltunk, akkor a rendszernek ki kell dobnia bennünket a bejelentkező lapra.

Ha rendben vagyunk, akkor a $this->userEllenorzo(); sort adjuk hozzá a termek_controller.php file szerkeszt() és torol() metódusainak elejéhez is.

A termékcsoportok esetében minden funkciót elérhetetlenné kívávunk tenni a mezei userek számára. Ehhez a beforeFilter() szűrőt fogjuk használni. Ez a szűrő mindne controller metódus előtt automatikusan meghívódik ha definiálva van. Ha használni akarjuk a /app/controllers/termekcsoportok_controller.php filet az alábbi állapotba kell hozni:

<?php
class TermekcsoportokController extends AppController {

  var $name = 'Termekcsoportok';
  var $scaffold;
	
  function beforeFilter(){
    $this->userEllenorzo();
    }
	
}
?>

Ennek hatására a termékcsoportok minden egyes metódusa előtt meghívódik az ellenörző függvényünk, vagyis ha nem vagyunk bejelentkeze akkor a http://localhost/~rrd/tulasi/termekcsoportok kezedtű URL-ek közül egyik sem lesz elérhető a számunkra, jobban modva mindegyik a belépő oldalra irányít bennünket.

Bevezettünk ezzel egy újabb biztonsági réteget csökkentve alkalmazásunk betörési pontjait. A következő részben kicsit megpróbáljuk növelni alkalmazásunk használhatóságát, és megpróbálunk átvergődni bizonyos karakterkódolási problémákon.

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöljük.