Drupal 7 bejelentkező adatok, jogosultságok külső adatbázisból

bykyny képe

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.

Drupal verzió: 
bykyny képe

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é.

0
0