Egy szolgáltató vállalkozás rendszerét kell elképzelni, amelynek Drupal 7 alapú weblapja van. A cég munkatársai, valamint az előfizetők is bejelentkezhetnek a weblapon, így jogosultságuk függvényében bizonyos menedzseléseket végezhetnek a szolgáltatással kapcsolatosan. A munkatársak belépési adatai és jogosultságai szokásos módon a Drupal jogosultsági rendszerében van megoldva, ezzel semmi probléma. A probléma lényege az, hogy az előfizetők bejelentkező adatai és jogosultságai 'külső' adatbázisban van tárolva, sőt ez akár több adatbázis táblájában is lehet. A menedzselési lehetőségek Drupal modulok formájában (főleg formok) van megvalósítva, a jogosultságok Drupál csoporttagságon keresztül érvényesülnek (modulhoz, vagy annak bizonyos funkcióihoz való hozzáférés). Az előfizetők bejelentkezési és jogosultság adatai a külső adatbázisokban például a következőképpen tárolódhatnak:
Adatbázisnév, táblanév: adatbazis-1, felhasznalok
Bejelentkező név mezőneve: login_name
Jelszó mező: password
Jogusultság mező: login_roles
A jogosultság értéke 'JOGOSULTSAG-1', 'JOGOSULTSAG-2', 'JOGOSULTSAG-3' lehet, illetve ezekből több is szerepelhet a mezőben vesszővel elválasztva.
Adatbázisnév, táblanév: adatbázis-2, ugyfelek
Bejelentkező név mezőneve: user_name
Jelszó mező: pass
Jogosultság mező: user_role
A jogosultság értéke ebben a táblában szereplő ügyfelekre nézve például csak 'FAX' lehet.
A jogosultságok tárolásának fentiekhez hasonló rendszerét adottnak kellet tekintenem a konkrét megvalósítás során.
A Drupal saját adatbázisa a 'default' adatbázis.
A felhasználó karbantartása (például jelszavának, jogosultságainak megváltoztatása) nem a Drupal adatbázisában, hanem a külső adatbázisban történik.
Egy fontos kikötés van: a bejelentkező neveknek az egész rendszerre vonatkozóan egyedinek kell lenniük!!
Megoldandó, hogy a felhasználók különböző helyekről (adatbázis táblákból) vett bejelentkező adatokkal bejelentkezhessenek a rendszerbe a weblapon keresztül, és bejelentkezés után a megfelelő csoportoknak legyenek tagjai.
A fenti probléma egyik lehetséges megoldását írom le, amelyhez a bejelentkezés folyamatát saját kézbe kell venni.
A MYMODULE helyett a modul nevét kell behelyettesíteni minden előforduláskor.
Először implementáljuk a hook_form_altert:
function MYMODULE_form_alter(&$form, $form_state, $form_id) { global $user; if ($form_id == 'user_login' or $form_id == 'user_login_block') { $form['#validate'] = array ('user_login_name_validate', 'MYMODULE_validate', 'MYMODULE_user_login_final_validate'); unset($form['links']); } }
Ezzel elérjük, hogy meghívódik az eredeti 'user_login_name_validate' függvény, majd az általunk írt 'MYMODULE_validate', és a 'MYMODULE_user_login_final_validate' függvények.
Az unset sor eltünteti a bejelentkező ablak linkjeit. Ennek miértjére később térek ki.
Ezután jön a legfontosabb rész, a MYMODULE_validate függvény:
function MYMODULE_validate ($form, &$form_state) { global $user; $password = trim($form_state['values']['pass']); $success = false; // Ha van bejelentkező név és jelszó megadva if (!empty($form_state['values']['name']) && !empty($password)) { // Szerepel a Drupal saját users táblájában? $count = db_query("SELECT COUNT(*) FROM {users} WHERE name = :name", array(':name' => $form_state['values']['name']))->fetchField(); if ($count == 0) { // Ha nem szerepel a users táblában, akkor felhasználó // azonosítás és beírás db_set_active('adatbazis-1'); // Bevitt bejelentkező név alapján kiolvassa a felhasználó adatait // az admin adatbázis customers táblájából (mail az email címe) $u_query = db_query("SELECT password, login_name, login_roles, mail FROM {felhasznalok} WHERE login_name = :login_name", array( ':login_name' => $form_state['values']['name'] ) ); db_set_active('default'); // Ha megtalálta if ($u_rec = $u_query->fetchAssoc()) { // Ha érvényes a jelszó if (MYMODULE_validate_password($password, $u_rec['password'])) { // Tömbbe a jogosultágok nevei $u_roles = explode(",", $u_rec['login_roles']); // Tömb az új felhasználó adataival $MYMODULE_auth_new_user = array( 'name' => $u_rec['login_name'], 'mail' => $u_rec['mail'], 'roles' => $u_roles ); // Sikerült azonosítani, így a többi adatbázisban nem fog keresni $success = true; } } if ( ! $success) { // Ha eddig nem sikerült azonosítani az ügyfelet, // akkor az előző részhez hasonlóan // a következő adatbázis (pl. az adatbazis-2 ugyfelek táblája) // segítségével megpróbáljuk azonosítani az ügyfelet // Ez az eljárás tetszőleges számban ismételhető, tetszőleges // számú adatbázistáblából próbálhatjuk meg az ügyfelet // azonosítani és hozzárendelni a megfelelő jogosultságot, // létrehozva a $MYMODULE_auth_new_user tömböt. } db_set_active('default'); // Ha valahol azonosította a felhasználót, akkor elmenti // az új felhasználó adatait és jogosultságait // a Drupal users és users_roles táblájába $saved = false; if ($success) { // Veszi a következő felhasználó azonosítót $uid = 1 + db_query("SELECT MAX(uid) FROM {users}")->fetchField(); // Ha az adatok ki vannak töltve if (isset($MYMODULE_auth_new_user)) { // Beírás a Drupal users táblájába db_merge('users') ->key(array('uid' => $uid)) ->fields( array( 'name' => $MYMODULE_auth_new_user['name'], 'mail' => $MYMODULE_auth_new_user['mail'], 'pass' => '-', 'created' => REQUEST_TIME, 'status' => 1 )) ->execute(); // Beolvassa egy tömbbe az összes lehetséges // jogosultság megnevezést, // hozzárendelve a megfelelő Drupal csoportot // (amelynek tagjává kell tenni, // ha rendelkezik az adott jogosultsággal). $role_role = array(); db_set_active('adatbazis-1'); $u_query = db_query("SELECT role_value, role_role FROM {customer_roles}"); foreach ($u_query as $u_rec) { $role_role[$u_rec->role_value] = $u_rec->role_role; } db_set_active('default'); // Majd egyenként elmenti a foreach ($MYMODULE_auth_new_user['roles'] as $role) { // A felhasználó jogosultságához megállapítja a // Drupal csoport azonosítóját a csoportnév alapján $rid = db_query("SELECT rid FROM {role} WHERE name = :name", array( ':name' => $role_role[$role] ))->fetchField(); // Elmenti a csoporttagságot a Drupal users_roles táblájába db_insert('users_roles') ->fields( array( 'uid' => $uid, 'rid' => $rid ))->execute(); } $saved = true; } } } // Ha szerepelt a users táblában, vagy az előző részben bekerült oda if ($count > 0 || ($success && $saved)) { // Ha már szerepel a drupal users táblájában // Itt egy darabig az eredeti Drupal kód if (!flood_is_allowed('failed_login_attempt_ip', variable_get('user_failed_login_ip_limit', 50), variable_get('user_failed_login_ip_window', 3600))) { $form_state['flood_control_triggered'] = 'ip'; return; } $account = db_query("SELECT * FROM {users} WHERE name = :name AND status = 1", array(':name' => $form_state['values']['name']) )->fetchObject(); if ($account) { if (variable_get('user_failed_login_identifier_uid_only', FALSE)) { // Register flood events based on the uid only, // so they apply for any // IP address. This is the most secure option. $identifier = $account->uid; } else { // The default identifier is a combination of uid // and IP address. This // is less secure but more resistant to // denial-of-service attacks that // could lock out all users with public user names. $identifier = $account->uid . '-' . ip_address(); } $form_state['flood_control_user_identifier'] = $identifier; // Dont allow login if the limit for this user has been reached. // Default is to allow 5 failed attempts every 6 hours. if (!flood_is_allowed('failed_login_attempt_user', variable_get('user_failed_login_user_limit', 5), variable_get('user_failed_login_user_window', 21600), $identifier)) { $form_state['flood_control_triggered'] = 'user'; return; } $success = false; // Ha a drupal users táblájából vette a jelszót, // akkor normál beléptetés if ($account->pass != '-') { $account = user_load_by_name($form_state['values']['name']); $uid = user_authenticate($form_state['values']['name'], $password); } else { $uid = FALSE; // Ha külső táblából történik az azonosítás db_set_active('adatbazis-1'); // admin adatbázis customers táblájából $u_query = db_query("SELECT password FROM {felhasznalok} WHERE login_name = :login_name", array(':login_name' => $form_state['values']['name']) ); db_set_active('default'); // Ha megtalálta if ($u_rec = $u_query->fetchAssoc()) { if (MYMODULE_validate_password($password, $u_rec['password'])) { $success = true; } } if ( ! $success) { // Ha a felhasznalok táblában nem találta, akkor // az előző részhez hasonlóan ellenőrzés a következő // adatbázisban, // és így egymás után, minden szükséges adatbázisban } if ($success) { $account = user_load_by_name($form_state['values']['name']); $uid = $account->uid; } } } if ($uid > 0) { $user = $account; } $form_state['uid'] = $uid; } } }
A MYMODULE_validate_password függvény a jelszó érvényességét ellenőrzi, ennek megvalósítása itt nem szerepel. Visszaadott értéke true, ha a jelszó érvényes.
Az adatbazis-1 customer_roles táblája tartalmazza az egész rendszer összes lehetséges külső adatbázis táblában szereplő jogosultság megnevezéseket, mindegyikhez hozzárendelve a megfelelő Drupal csoport nevét, például:
role_value role_role
JOGOSULTSAG-1 drupal_csoport-1
JOGOSULTSAG-2 drupal_csoport-2
JOGOSULTSAG-3 drupal_csoport-3
FAX drupal_csoport-4
A drupal_csoport-1 - 4 csoportoknak létezniük kell, azaz előzőleg létre kell hozni a Drupalban, és beállítani a megfelelő jogosultságait (pl. tartalmak megtekintése).
A customer_roles táblából állapítja meg a rendszer, hogy a felhasználót mely Drupal csoport tagjává kell tennie.
Nézzük azt az esetet, amikor az 'adatbazis-1' 'felhasznalok' táblájába valamilyen módon felvitelre került egy új felhasználó, és az először próbál bejelentkezni a weblapon keresztül a rendszerbe.
Ekkor a Drupal users táblájában nyilvánvalóan nem fogja megtalálni a felhasználót. Utána megpróbálja azonosítani a 'felhasználok' tábla segítségével, és ez ebben az esetben sikerül is (illetve, ha ott sem találná, akkor sorban az összes megadott táblában próbálná).
Azonosítás után jelszóellenőrzés, és ha egyezik, akkor a MYMODULE_auth_new_user tömböt létrehozza az új felhasználó adataival.
Ezután a következő felhasználó azonosítóval elmenti az új felhasználót a Drupal users táblájába.
Itt azonban van egy fontos dolog, mégpedig az, hogy jelszónak egy '-' jelet mentünk el a users táblába, amely azt eredményezi, hogy a későbbiekben nem fogja tudni beléptetni a felhasználót ebből a táblából vett jelszóval, és ezért keresni fogja az egyéb lehetséges helyeken (adatbázis táblákban) az érvényes felhasználónév-jelszó párost.
Végül elmenti az új felhasználó csoporttagságait a Drupal users_roles táblájába.
Ezután folytatódik a futás a második résszel, amely akkor fut le, ha szerepel a felhasználó a Drupal users táblájában. Már szerepel az új felhasználó is, hiszen most inzertáltuk be oda. Létrehozza a Drupal users tábla adataiból az $account objektumot. Ha a jelszó nem '-', akkor normál Drupal bejelentkezés történik.
A '-' jelszó azt jelzi, hogy más adatbázistáblából kell venni a jelszót. Ekkor sorban megvizsgálja a lehetséges táblákban, és jelszóellenőrzés történik.
Ha valamelyik egyezik, akkor a bejelentkezteti a felhasználót. A felhasználó azonosítója (uid) a Drupal users táblában lévő azonosító lesz, és a felhasználó bejelentkezés után tagja lesz a megfelelő csoport(ok)nak is.
Mint látjuk, első alkalommal kétszer történik a felhasználó jelszavának ellenőrzése, és minden későbbi bejelentkezéskor már meg fogja találni a Drupal users táblájában, és ennek megfelelően csak a második rész fut le.
A MYMODULE_user_login_final_validate implementálása:
function MYMODULE_user_login_final_validate ($form, &$form_state) { global $user; if (!$user->uid) { form_set_error('name', t('Sorry, unrecognized username or password.')); } }
Ez az eredeti függvény kissé leegyszerűsített (és így funkcióvesztéssel járó!!) implementációja. Akinek szüksége van a teljes funkcionalitásra, az implementálhatja az eredetit.
A lényeg a következő: A felhasználók jelszavait nem a Drupal users táblájában, hanem a külső adatbázistáblákban tartjuk karban (minden bejelentkezéskor onnan kell vennie a rendszernek). Ha megengedjük, hogy sikertelen bejelentkezés után például a Drupal új jelszót generáljon (küldjön emailben) az ügyfélnek, akkor ez átírja a '-' jelszót a Drupal users táblájában, és a felhasználó függetlenül a külső táblákban szereplő jelszótól, a rendszerbe be tud jelentkezni.
Hogy ez ne következhessen be, le kell tiltani minden olyan lehetőséget, amellyel a Drupal users tábla jelszava megváltozhat. Ezt a célt szolgálja fentebb a MYMODULE_form_alter unset utasítássora is. A MYMODULE_user_login_final_validate függvényben látható, hogy csak hibaüzenet van, szemben az eredeti megoldással, amelyben lehet új jelszót létrehozni.
Ahhoz, hogy a felhasználó mégis meg tudja változtatni belépési jelszavát, külön modult (menüpont a bejelentkezett felhasználó számára) kell csinálni erre a célra, amely a megfelelő adatbázistáblában változtatja meg a jelszót.
Elképzelhető, hogy azt is meg lehet oldani, hogy a Drupal eredeti jelszókezelő függvényei a külső adatbázisokba mentse a jelszóváltozást, ennek még nem néztem utána.
Ha változik a felhasználó külső adatbázisban nyilvántartott csoporttagsága, akkor a felhasználót törölni kell a Drupal users táblájából, valamint a csoporttagságait a Drupal users_roles táblájából. Vagy a jogosultság módosító modul megfelelően módosítja a Drupal users_roles tábláját is.
Le kell tiltani a felhasználó saját accountjának menedzselését.
Az eredeti, általam írt modul egyszerűsítése, lényege szerepel itt, remélem semmi zavaró hibát nem sikerült elkövetnem az egyszerűsítéskor. Talán lesz, aki hasznosnak találja.
Javítás
Bocs, de a megjegyzésben
az admin adatbázis customers táblájából
helyett
az adatbazis-1 adatbázis felhasznalok táblájából
a helyes a MYMODULE_validate eleje felé.