Yii blog tutorial néhány extrával (3. rész)

A korábbi ígéretemhez híven a tavaly elkezdett Yii alapú blog tutorial folytatódik, így ebben a részben tovább lépünk a felhasználók hitelesítésére és az RBAC komponens beállítására.


Az előző cikk megjelenése óta kijött a Yii keretrendszer 1.1.14 verziója, ezért jobbnak láttam újragenerálni a projektet és lépésenként átnézni a korábbi konfigurációt.

Tipp! Mostantól a cikksorozat összes részéhez a legfrissebb forrásfájlok lesznek feltöltve.

Adatbázis módosítása

Az új verzióban bevezették a CPasswordHelper osztályt, ami beépített megoldást jelent a jelszó hash készítésére és ellenőrzésére. Nyissuk meg a korábbi schema.mysql.sql fájlt és módosítsuk az alábbiak szerint.

Felhasználó tábla

Első lépésben módosítsuk a felhasználó tábla password mezőjének hosszúságát.

// schema.mysql.sql
CREATE TABLE `tbl_user` (
    // ...
    `password` VARCHAR(64) NOT NULL
    // ...
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
// ...

Adatok feltöltése

Az adatok feltöltésénél is módosítsuk a webtuts felhasználóhoz tartozó demo jelszó hash változatát.

// schema.mysql.sql
INSERT INTO `tbl_user` (`username`, `password`, `email`, `status`, `logins`, `create_time`, `last_time`) VALUES
    ('webtuts', '$2a$13$H4ocZuEsP0e5Vr2PkW1ukONXbHs4f1t.8KMrmwdVrO8C/JgVxZQRm', 'info@webtuts.hu', 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
// ...

Felhasználók hitelesítése

A felhasználók hitelesítését a Yii keretrendszer az IUserIdentity interfészt támogató osztályokkal végzi, hogy többféle hitelesítési eljárást is használhassunk. Például a hagyományos űrlap alapú módszer mellett készíthetünk Facebook fiók alapú bejelentkezést is.

Felhasználó modell

A felhasználó modell létrehozásához nyissuk meg a Gii generáló eszközt az alábbi link segítségével:

http://localhost/blog/gii

Ezután lépjünk be a beállításoknál megadott jelszóval, majd kattintsunk a Model Generator menüpontra. A Table Name mezőhöz kezdjük el beírni a tbl_user táblanevet, és válasszuk ki a legördülő menüből. Ha mindent jól csináltunk, a Preview gomb megnyomása után felkínálja, hogy létrehozza a models\User.php osztályt. A véglegesítéshez nyomjuk meg a Generate gombot.

Nyissuk meg a létrejött User.php fájlt, és a az osztály elején definiáljuk az Aktivált, Letiltott és Nem aktivált állapotokhoz tartozó konstansokat.

// User.php
class User extends CActiveRecord
{
    const STATUS_ACTIVATED = 1;
    const STATUS_BANNED = 2;
    const STATUS_NOT_ACTIVATED = 3;
    // ...
}

Ezután keressük meg az attributeLabels() metódust, és a Yii::t() fordítóeszköz segítségével írjuk át az oszlopneveket magyarra. Emlékezzünk rá, hogy korábban magyar forrásnyelvet állítottunk be, így használjunk magyar alapértelmezést.

Tipp! Ha a jövőben nem tervezzük többnyelvűre bővíteni oldalunkat, a Yii 1.1.14 verziójától használhatjuk az adattáblák oszlopaihoz rendelhető kommenteket is a generálásnál.

// User.php
class User extends CActiveRecord
{
    // ...
    public function attributeLabels()
    {
        return array(
            'id' => Yii::t('app', 'ID'),
            'username' => Yii::t('app', 'Felhasználónév'),
            'password' => Yii::t('app', 'Jelszó'),
            'email' => Yii::t('app', 'E-mail'),
            'status' => Yii::t('app', 'Állapot'),
            'logins' => Yii::t('app', 'Belépve'),
            'profile' => Yii::t('app', 'Profil'),
            'create_time' => Yii::t('app', 'Létrehozva'),
            'last_time' => Yii::t('app', 'Utolsó látogatás'),
        );
    }
    // ...
}

Az osztályt bővítsük ki két újabb metódussal, az egyik összehasonlítja a felhasználó jelszavát a megadottal, a másik elkészíti a jelszó hash változatát.

Tipp! A validációs szabályokkal és relációkkal a későbbiekben foglalkozunk.

// User.php
class User extends CActiveRecord
{
    // ...
    public function validatePassword($password)
    {
        return CPasswordHelper::verifyPassword($password, $this->password);
    }

    public function hashPassword($password)
    {
        return CPasswordHelper::hashPassword($password);
    }
}

UserIdentity osztály módosítása

Az alkalmazás generálásakor elkészült az alapértelmezett <alkalmazas_helye>/protected/components/UserIdentity.php fájl, amely egy statikus tömbben tárolt felhasználónév és jelszó párokkal valósít meg hitelesítést. A következő kódrészlettel ennek a viselkedését alakítjuk át úgy, hogy az adatbázisban tárolt felhasználókat hitelesítjük. Első lépésben itt is elkészítjük a UserIdentity osztály Letiltott és Nem aktivált állapothoz tartozó konstansait.

// UserIdentity.php
class UserIdentity extends CUserIdentity
{
    const ERROR_STATUS_BANNED = 3;
    const ERROR_STATUS_NOT_ACTIVATED = 4;
    // ...
}

Ha megvan, adjuk hozzá a felhasználó azonosítóját tároló adattagot és getter metódust.

// UserIdentity.php
class UserIdentity extends CUserIdentity
{
    // ...
    private $_id;

    public function getId()
    {
        return $this->_id;
    }
}

Végül adjuk hozzá az authenticate() metódust, amely működését tekintve megpróbálja megkeresni a felhasználót az adatbázisban. Ha megtalálta, összehasonlítja a megadott jelszóval, illetve ellenőrzi az állapotát. Visszatérési értékként logikai igaz vagy hamis formájában tájékoztat az eredményről, de a CBaseUserIdentity ősosztályban definiált konstansok és az errorCode adattag segítségével a konkrét hiba is behatárolható.

// UserIdentity.php
class UserIdentity extends CUserIdentity
{
    // ...
    public function authenticate()
    {
        $user = User::model()->find('LOWER(username)=?', array(mb_strtolower($this->username)));

        if ($user === null) {
            $this->errorCode = self::ERROR_USERNAME_INVALID;
        } elseif (!$user->validatePassword($this->password)) {
            $this->errorCode = self::ERROR_PASSWORD_INVALID;
        } elseif ($user->status == User::STATUS_BANNED) {
            $this->errorCode = self::ERROR_STATUS_BANNED;
        } elseif ($user->status == User::STATUS_NOT_ACTIVATED) {
            $this->errorCode = self::ERROR_STATUS_NOT_ACTIVATED;
        } else {
            $this->_id = $user->id;
            $this->username = $user->username;
            $this->errorCode = self::ERROR_NONE;
        }
        return $this->errorCode == self::ERROR_NONE;
    }
    // ...
}

WebUser osztály létrehozása

A fenti módosítások után már tökéletesen működik a bejelentkezés, de nem frissül a felhasználó tábla logins és last_login mezője. Ráadásul engedélyezve van az automatikus bejelentkezés is, amelynél szintén szeretnénk frissíteni a két mezőt. Ennek megoldására készítsünk egy WebUser nevű osztályt az <alkalmazas_helye>/protected/components/ mappába és származtassuk a CWebUser osztályból.

// WebUser.php
class WebUser extends CWebUser {}

Ezután az ősben található afterLogin() metódus felülírásával frissítsük be a szükséges mezőket.

// WebUser.php
class WebUser extends CWebUser
{
    protected function afterLogin($fromCookie)
    {
        $user = User::model()->findByPk($this->id);
        $user->logins++;
        $user->last_time = time();
        $user->save(false);
    }
}

Utolsó lépésként állítsuk be a main.php konfigurációs fájlban a felhasználó komponenshez tartozó osztályt az újonnan létrehozott WebUser osztályra.

// main.php
return array(
    // ...
    'components' => array(
        // ...
        'user' => array(
            'class' => 'WebUser',
            // ...
        ),
        // ..
    ),
    // ...
);

Tipp! A bejelentkezési üzenetek lefordítását és kibővítését a LoginForm modellben lehet elvégezni, amely megtalálható a letölthető forrásfájlok között.

RBAC komponens beállítása

Az RBAC a Role-Based Access Control szavak rövidítése, ami lényegében szerepkör alapú jogosultságkezelést jelent. A Yii beépített RBAC rendszeréhez a cikksorozat előző részében elkészítettük az adattáblákat, így most kezdjük el a komponens beállítását.

Auth Manager beállítása

A main.php konfigurációs fájlban vegyük fel a CDbAuthManager komponenst, és állítsuk be az adatbázis kapcsolatot az adattáblákkal együtt.

// main.php
return array(
    // ...
    'components' => array(
        // ...
        'authManager' => array(
            'class' => 'CDbAuthManager',
            'connectionID' => 'db',
            'itemTable' => '{{auth_item}}',
            'itemChildTable' => '{{auth_item_child}}',
            'assignmentTable' => '{{auth_assignment}}',
        ),
        // ...
    ),
    // ...
);

RBAC elemek létrehozása

Az Auth Manager komponens három elemtípussal rendelkezik:

  1. Művelet (Operation)
  2. Feladat (Task)
  3. Szerepkör (Role)

Az elemek között hierarchia építhető fel, és minden elemhez üzleti logika, egyfajta megkötés is rendelhető, amely PHP kifejezésként lesz értelmezve.

Tipp! A Yii 2 RBAC rendszerében megszűntek a műveletek és feladatok, helyettük bevezették az engedély (permission) elemtípust.

A megadott üzleti logikát felhasználva a szerepkörök beállíthatók alapértelmezettnek is, ilyenkor a rendszer automatikusan osztja ki őket. A blog esetén a speciális jogkört igénylő műveleteket a könnyebb kezelhetőség érdekében a feladatok alá csoportosítom, és létrehozok egy adminisztrátor szerepkört.

  • Adminisztrátor (Szerepkör)
    • Bejegyzések kezelése (Feladat)
      • Új bejegyzés hozzáadása (Művelet)
      • Bejegyzés szerkesztése (Művelet)
      • Bejegyzés törlése (Művelet)
    • Kommentek kezelése (Feladat)
      • Komment szerkesztése (Művelet)
      • Komment törlése (Művelet)

A hierarchia felépítéséhez az Auth Manager beépített metódusaival hozzuk létre az elemeket. Mivel adatbázisban tárolódnak, az alábbi kódrészletet csak egyszer futtassuk le.

$authManager = Yii::app()->authManager;

$role = $authManager->createRole('admin', 'Adminisztrátor');
$role->assign(1); // A korábban létrehozott felhasználó legyen admin

$authManager->createOperation('createPost', 'Új bejegyzés hozzáadása');
$authManager->createOperation('updatePost', 'Bejegyzés szerkesztése');
$authManager->createOperation('deletePost', 'Bejegyzés törlése');

$task = $authManager->createTask('managePosts', 'Bejegyzések kezelése');
$task->addChild('createPost');
$task->addChild('updatePost');
$task->addChild('deletePost');

$role->addChild('managePosts');

$authManager->createOperation('updateComment', 'Komment szerkesztése');
$authManager->createOperation('deleteComment', 'Komment törlése');

$task = $authManager->createTask('manageComments', 'Kommentek kezelése');
$task->addChild('updateComment');
$task->addChild('deleteComment');

$role->addChild('manageComments');

Adatok feltöltése

Nyissuk meg a schema.mysql.sql fájlt, és adjuk hozzá a következő sorokat, hogy ne kelljen az alkalmazás minden telepítésénél lefuttatni a fenti kódot.

INSERT INTO `tbl_auth_item` (`name`, `type`, `description`, `bizrule`, `data`) VALUES
    ('admin', 2, 'Adminisztrátor', NULL, 'N;'),
    ('createPost', 0, 'Új bejegyzés hozzáadása', NULL, 'N;'),
    ('deleteComment', 0, 'Komment törlése', NULL, 'N;'),
    ('deletePost', 0, 'Bejegyzés törlése', NULL, 'N;'),
    ('manageComments', 1, 'Kommentek kezelése', NULL, 'N;'),
    ('managePosts', 1, 'Bejegyzések kezelése', NULL, 'N;'),
    ('updateComment', 0, 'Komment szerkesztése', NULL, 'N;'),
    ('updatePost', 0, 'Bejegyzés szerkesztése', NULL, 'N;');

INSERT INTO `tbl_auth_item_child` (`parent`, `child`) VALUES
    ('managePosts', 'createPost'),
    ('manageComments', 'deleteComment'),
    ('managePosts', 'deletePost'),
    ('admin', 'manageComments'),
    ('admin', 'managePosts'),
    ('manageComments', 'updateComment'),
    ('managePosts', 'updatePost');

INSERT INTO `tbl_auth_assignment` (`itemname`, `userid`, `bizrule`, `data`) VALUES
    ('admin', 1, NULL, 'N;');

Kapcsolódó bejegyzések