سلام 🙂
خيلي از مقالاتي كه در مورد مديريت دسترسي كاربران بحث مي كنند،فقط به نحوه اعتبارسنجي كاربران اشاره دارند كه صرفا داراي دو سطح امنيتي مي باشد:يا كاربر وارد سيستم شده(logged in) و يا نشده(not logged in).براي بسياري از وب سايت ها درجه بالاتري از كنترل نياز است تا بتوانند مشخص كنند كه كاربران شان به چه جاهايي دسترسي داشته باشند و چه كارهايي بتوانند انجام دهند.
ايجاد يك ليست كنترل دسترسي(ACL) مي تواند با انعطاف پذيري خود،دست شما را در انجام اين كار باز بگذارد.
مقدمه
تصور كنيد يك وبسايت بزرگ داريد كه به كاربران اجازه مي دهد تا طيف وسيعي از تكنيك هاي طراحي و برنامه نويسي وب را ياد بگيرند.علاوه بر اين كاربران معمولي،شما تعدادي كاربر ويژه(پولي) داريد و به اينها،نويسندگان سايت و مديران را نيز اضافه كنيد.
مشكل شما
شما مي خواهيد كاري كنيد كه كاربران فقط در حيطه نوع حساب كاربري خودشان،به محتويات سايت دسترسي داشته باشند.
راه حل
با ايجاد يك ليست كنترل دسترسي،شما قادر خواهيد بود به صورت ريز به ريز تعيين كنيد كه چه كاربراني به چه قسمت هايي از سايت دسترسي داشته باشند يا نه.
اگر نمونه نمايشي اين مطلب را ببينيد،با يك صفحه index مواجه مي شويد كه ACL را براي هر كاربر امتحان مي كند.شما با انتخاب لينك هاي پايين اين صفحه مي توانيد ACL مربوط به ديگر كاربران را نيز مشاهده كنيد.همچنين اگر روي لينك ‘Admin Screen’ در بالاي صفحه كليك كنيد،مي توانيد نمونه اي از رابط مديريت را ببينيد كه به شما اجازه مي دهد،كاربران،وظايف و دسترسي ها را مديريت كنيد.نكته:سيستم مديريتي هر 30 دقيقه همه چيز را از پايگاه داده به روز رساني خواهد كرد تا از بروز بودنشان مطمئن شود.همچنين در فايل هاي قابل دانلود،مسئله امنيت بخش مديريت نيز جاسازي شده است،بنابراين اگر كاربر شماره يك،اجازه ‘access admin’ را نداشته باشد،شما قادر نخواهيد بود به بخش مديريت دسترسي پيدا كنيد.
اين سيستم شما را قادر خواهد كرد كه گروه هاي مختلف كاربري ايجاد كنيد(مثل:مهمان،كاربران ويژه،نويسندگان يا مديران).به علاوه ما قادر خواهيم بود براي هر گروه يا به صورت انفرادي براي هر كاربر،دسترسي هاي جداگانه اي را تعيين كنيم.اجازه دهيد كارمان را با تنظيم پايگاه داده MySQL شروع كنيم.
قدم 1:ايجاد پايگاه داده
ACL ما در 6 جدول به هم مرتبط،ذخيره خواهد شد(شامل جدولي براي كاربران).شما بايد قبلا يك پايگاه داده را در محيط ميزبان خود(چه محلي و چه وب) ايجاد كرده باشيد.ساختار جداول ما به اين شكل خواهد بود:
كدي كه براي ايجاد پايگاه داده(شامل جداول) لازم است،در كد منبع موجود است(install.sql).همچنين يك فايل ديگر(sampleData.sql) نيز موجود است كه براي شما 4 كاربر نمايشي با وظايف و دسترسي هاي مختلف ايجاد مي كند تا با آنها سيستم را تست كنيد.آسان ترين راه براي اجراي فايل هاي فوق اين است كه آنها را در ويرايشگر دلخواه خود باز كرده و كدهايشان را در پنل SQL برنامه phpMyAdmin كپي كنيد.
قدم 2:اتصال به پايگاه داده
ما نياز به ايجاد يك فايل include داريم تا بتوانيم به پايگاه داده متصل شويم.يك فايل با نام assets/php/database.php ايجاد كرده و كدهاي زير را به آن اضافه كنيد(مقادير متغير ها را با اطلاعات مربوط به سرور خودتان تغيير دهيد)
<?php session_start(); ob_start(); $hasDB = false; $server = 'localhost'; $user = 'root'; $pass = 'mysql'; $db = 'acl_test'; $link = mysql_connect($server,$user,$pass); if (!is_resource($link)) { $hasDB = false; die("Could not connect to the MySQL server at localhost."); } else { $hasDB = true; mysql_select_db($db); } ?>
در خط اول ما،session_start() را فراخواني كرديم.در حقيقت ما از متغيرهاي جلسه استفاده نخواهيم كرد ولي شما به عنوان قسمتي از سيستم اعتبارسنجي كاربران،به آن نياز خواهيد داشت.بعد با فراخواني ob_start()،يك بافر خروجي ايجاد كرديم.عموما،وقتي php صفحه اي را ايجاد كرد،آن را به عنوان صفحه ساخته شده،به مرورگر مي فرستد.اگر از تابع ob_start استفاده كنيم،صفحه و هدرهاي آن تا زماني كه كامل بارگذاري نشوند،يا تا زماني كه ما تابع ob_end_flush() را فراخواني كنيم،به مرورگر فرستاده نخواهند شد.با استفاده از قابليت بافر كردن صفحه،ما مي توانيم علاوه بر بالاي صفحه،در هر نقطه اي از برنامه كه بخواهيم،بيننده را منتقل(redirect) كنيم.بعد از ارسال هدرها،تنها گزينه ي ما براي اين كار،استفاده از جاوااسكريپت خواهد بود و يك هكر با تجربه مي تواند با خاموش كردن جاوااسكريپت،براي ما دردسر ساز شود.اين يك خط به ما اجازه مي دهد اگر نياز بود،بتوانيم در هر نقطه اي از صفحه،اجازه دسترسي را به كاربر ندهيم.
خط 4 تا 8 متغير هاي ما را تنظيم مي كنند.$hasDB كه از نوع بولين مي باشد،براي تشخيص متصل بودن به پايگاه داده بكار مي رود.چهار متغير بعدي نيز براي اتصال به پايگاه داده كاربرد دارند.در خط 9 ه پايگاه داده متصل مي شود و در خط 10 چك مي كند كه آيا اتصال انجام شد يا خير؟اگر همه چيز درست بود،در خط 15 ديتابيس مان را انتخاب مي كنيم وگرنه بوسيله تابع die،يك خطا نمايش داده و اجراي برنامه متوقف مي شود.
قدم 3:ايجاد كلاس ACL
انصافا اين بخش مقداري طولاني است چون قرار است كلاس ACL را كه پايه سيستم ما است،ايجاد كنيم.بدين خاطر،براي طولاني بودن آن،پوزش مي خواهم.
سيستم ACL ما،شي گرا(Object Oriented Programming) مي باشد،براي همين ابتدا فايل حاوي كلاس را ايجاد مي كنيم.كارمان را با تعريف كلاس،خاصيت هاي كلاس و متد سازنده(constructor__) شروع مي كنيم و در فايلي بنام assets/php/class.acl.php ذخيره مي كنيم:
<?php class ACL { var $perms = array(); //Array : Stores the permissions for the user var $userID = 0; //Integer : Stores the ID of the current user var $userRoles = array(); //Array : Stores the roles of the current user function __constructor($userID = '') { if ($userID != '') { $this->userID = floatval($userID); } else { $this->userID = floatval($_SESSION['userID']); } $this->userRoles = $this->getUserRoles('ids'); $this->buildACL(); } function ACL($userID='') { $this->__constructor($userID); }
تحليل كد
بعد از تعريف كلاس،سه متغير يا خاصيت كه اطلاعات مربوط به ايجاد ACL را در خود خواهند داشت،ايجاد مي كرديم.
متد سازنده(Constructor)
تابع يا متد Constructor،اولين متدي است كه به صورت اتوماتيك،هنگام ايجاد شي(Object) از روي كلاس(Class)،فراخواني مي شود.يعني هر وقت كه از ;new ACL استفاده كنيم.بعد يك آرگومنت اختياري از ID كاربر به آن پاس داده شده است تا ACL يا (ليست كنترل دسترسي ها) را بر طبق آن بارگذاري كند.ما چك مي كنيم كه آيا ID كاربر به آن پاس داده شده يا نه؟اگر ID پاس داده نشده بود،فرض مي كنيم كه مي خواهيم ACL را براي كاربري كه در حال حاضر وارد شده(logged in) است،بارگذاري(Load) كنيم بنابراين مقدار موجود در متغير جلسه (Session) آن را مي خوانيم.در غير اين صورت،اگر اين مقدار پاس داده شده بود،ما مي توانيم ACL را براي كاربراني غير از كاربر جاري كه وارد شده است نيز تغيير دهيم و اين براي سيستم مديريت لازم خواهد بود يعني در جايي كه شما به عنوان مدير سيستم مي خواهيد ليست دسترسي هاي كاربري را تغيير دهيد.
بعد از خواندن ID كاربر،مي رويم سراغ متد getUserRoles تا آرايه اي از رفتارهاي تخصيص داده شده به كاربر را در داخل متغير كلاس يا همان خاصيت $userRoles ذخيره كنيم.در انتهاي متد سازنده نيز با فراخواني متد buildACL() مي توانيم ACL واقعي و مورد نظرمان را ايجاد كنيم.متدي كه در انتها مي بينيد،عصاي دست php4 مي باشد.هنگامي كه ما در php5،كد new ACL() را براي ايجاد شي،فراخواني مي كنيم،مفسر php متد __constructor() را اجرا مي كند اما اگر همين كد را در php4 اجرا كنيد،مفسر متد ACL() را اجرا مي كند.يعني مفسر php4 اول همان متدي را فراخواني و به جاي متد سازنده در php5 مي شناسد،كه همنام كلاس باشد.بنابراين با نوشتن متدي همنام با كلاس مان مي توانيم از سازگاري برنامه با php4 نيز مطمئن شويم.
از اين به بعد هر موقع ما شي ACL را با پاس دادن ID كاربري به آن،ايجاد كنيم،اين شي مجوزهاي دسترسي همان كاربر را در خود نگه خواهد داشت.
متدهاي كمكي(Helper Methods)
حالا اجازه دهيد تعدادي متد كمك كننده ديگر را به مان كلاس اضافه كنيم.اين متدها با انجام كارهاي اختصاص يافته به خودشان،پشتيبان متدهاي ديگر خواهند بود.
function getUserRoles() { $strSQL = "SELECT * FROM `user_roles` WHERE `userID` = " . floatval($this->userID) . " ORDER BY `addDate` ASC"; $data = mysql_query($strSQL); $resp = array(); while($row = mysql_fetch_array($data)) { $resp[] = $row['roleID']; } return $resp; } function getAllRoles($format='ids') { $format = strtolower($format); $strSQL = "SELECT * FROM `roles` ORDER BY `roleName` ASC"; $data = mysql_query($strSQL); $resp = array(); while($row = mysql_fetch_array($data)) { if ($format == 'full') { $resp[] = array("ID" => $row['ID'],"Name" => $row['roleName']); } else { $resp[] = $row['ID']; } } return $resp; } function buildACL() { //first, get the rules for the user's role if (count($this->userRoles) > 0) { $this->perms = array_merge($this->perms,$this->getRolePerms($this->userRoles)); } //then, get the individual user permissions $this->perms = array_merge($this->perms,$this->getUserPerms($this->userID)); } function getPermKeyFromID($permID) { $strSQL = "SELECT `permKey` FROM `permissions` WHERE `ID` = " . floatval($permID) . " LIMIT 1"; $data = mysql_query($strSQL); $row = mysql_fetch_array($data); return $row[0]; } function getPermNameFromID($permID) { $strSQL = "SELECT `permName` FROM `permissions` WHERE `ID` = " . floatval($permID) . " LIMIT 1"; $data = mysql_query($strSQL); $row = mysql_fetch_array($data); return $row[0]; } function getRoleNameFromID($roleID) { $strSQL = "SELECT `roleName` FROM `roles` WHERE `ID` = " . floatval($roleID) . " LIMIT 1"; $data = mysql_query($strSQL); $row = mysql_fetch_array($data); return $row[0]; } function getUsername($userID) { $strSQL = "SELECT `username` FROM `users` WHERE `ID` = " . floatval($userID) . " LIMIT 1"; $data = mysql_query($strSQL); $row = mysql_fetch_array($data); return $row[0]; }
متد getUserRoles
متد getUserRoles() وظايف كاربر جاري را در قالب يك آرايه بر خواهد گرداند.ابتدا،دستور SQL مناسب را نوشته و اجرا مي كنيم.بوسيله حلقه while()،بين نتايج يافت شده،يك حلقه ايجاد مي كنيم تا ID را يكي يكي به آن اضافه كنيم.در پايان آرايه اي از ID را در اختيار داريم.همچنين متد getAllRoles() تمام وظايف موجود را بر مي گرداند(نه فقط يكي از انها را).بوسيله آرگومنت $format،اين متد مي تواند آرايه اي شامل ID تمام وظايف را برگردانذ يا بوسيله يك آرايه انجمني(آرايه اي كه ايندكس آن علاوه بر عدد مي تواند هر چيزي باشد)،ID و به همراه عنوان وظابف را نيز برگشت دهد.به اين ترتيب بوسيله يك متد،دو كار انجام مي دهيم و متد بعدي تو جيب مان مي ماند!اگر بخواهيم آرايه شرح وظايف كاربر را در MySQL استفاده كنيم،نياز به آرايه اي داريم كه فقط شامل ID وظايف(Roles) باشد ولي چون ما مي خواهيم آنها را در صفحه اي نشان دهيم،راه دوم كار ما را بسيار آسان مي كند.
متد getUserRoles() وظايف كاربر جاري را در قالب يك آرايه بر خواهد گرداند.ابتدا،دستور SQL مناسب را نوشته و اجرا مي كنيم.بوسيله حلقه while()،بين نتايج يافت شده،يك حلقه ايجاد مي كنيم تا ID را يكي يكي به آن اضافه كنيم.در پايان آرايه اي از ID را در اختيار داريم.همچنين متد getAllRoles() تمام وظايف موجود را بر مي گرداند(نه فقط يكي از انها را).
buildACL
buildACL() متدي است كه آرايه دسترسي هاي كاربر را ايجاد مي كند و حكم قلب سيستم ما را دارد.اول چك مي كنيم كه آيا نقشيبه كاربر محول شده است يا نه.اگر اين طور بود،با استفاده از array_merge()،آرايه قبلي حاوي دسترسي ها را با آرايه جديدي كه متد getRolePerms() بر مي گرداند،تركيب مي كنيم(بدين ترتيب تمام مجوزهاي دسترسي مربوط به تمام رفتارهاي محوله به كاربر ار خواهيم داشت).بعد همين كار را با فراخواني متد getUserPerms() براي دسترسي هاي مخصوص كاربر نيز انجام مي دهيم.اين مهم است كه ما مجوزهاي دسترسي كاربر را دوبار بخوانيم زيرا تابع array_merge() كليدهاي تكراري را بازنويسي مي كند.با اين كار مطمئن خواهيم شد دسترسي هاي مختص كاربر با دسترسي هاي ارثي مربوط به رفتارها جايگزين خواهند شد.
همه توابع getPermKeyFromID()،getPermNameFromID()،getRoleNameFromID() و getUsername() تقريبا مشابه هم هستند و به ما اجازه مي دهند تا يك ID را به آنها پاس دهيم تا يك مقدار متني مناسب به ما باز گردانند.شما مي توانيد ببينيد كه چگونه بعد از ايجاد كدهاي SQL مناسب،آنها را اجرا كرديم و نتيجه بازگردانده شد.
در بخش بعدی دو متد ایجاد خواهیم کرد تا دسترسي های(permissions) كاربر را از پايگاه داده استخراج كنند…
سلام وقت بخیر
در قسمت راه حل فرمودین که” اگر نمونه نمایشی این مطلب را ببینید،با یک صفحه index مواجه می شوید که ACL را برای هر کاربر امتحان می کند.شما با انتخاب لینک های پایین این صفحه می توانید ACL مربوط به دیگر کاربران را نیز مشاهده کنید.همچنین اگر روی لینک ‘Admin Screen’ در بالای صفحه کلیک کنید،می توانید نمونه ای از رابط مدیریت را ببینید” اما لینکی من پیدا نکردم،تو بخش بعد اضافه میکنید؟
میشه ادامش بدین
با سلام و تشکر از سایت خوبتون
در قدم 2 از لحاظ ترجمه مشکل وجود داره :
“تا زمانی که ما تابع ob_end_flush() را فراخوانی کنیم،به مرورگر فرستاده نخواهند شد.با استفاده از قابلیت بافر کردن صفحه،ما می توانیم(((((علاوه بر بالای صفحه))))،در هر نقطه ای از برنامه که بخواهیم،بیننده را منتقل(redirect) کنیم”
:منظور از header در متن اصلی تابع header() بوده نه بالای صفحه.
ممنون.
سلام
اون “علاوه بر بالای صفحه” که فرمودین مربوط به این بخش هست :”instead of just at the top”
ممنون از اینکه با دقت و حساسیت مطالب رو دنبال می کنید
موفق باشید
سپاس …
منتظر بقیشم !!! 🙂