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.