الإدخال والتعديل بشكل آمن في لارافيل ومشكلة Mass-Assignment

يوضح المقال سبب حدوث خطأ MassAssignmentException في لارافيل وطريقة حل المشكلة باستخدام الحلول والأدوات التي توفرها Laravel.

عمار الخوالدةمنذ 6 سنوات

مقدمة

توفر لارافيل طرقا سهلة للتعامل مع قواعد البيانات باستخدام Eloquent ORM بحيث تمثل الجداول فيه على شكل Models ويصبح التعامل معه سهلا جدا باستخدام بعض الدوال الجاهزة Methods، ومن العمليات المهمة التي نستخدمها دائما في أي مشروع، عملية الإدخال (Insertion/Creation) والتعديل (Edit/Update)، وتوفر لارافيل طرقا سهلة لفعل ذلك، وللتوضيح فقط في هذا الدرس، سنفترض وجود Model باسم User، له الخصائص الآتية:

  • id وهو الـ Primary Key
  • name
  • email
  • password
  • bio: وصف مختصر للمستخدم، وهي قيمة اختيارية ( لن نجبر المستخدم على ادخالها ) لكنها لا تقبل null، قيمتها الافتراضية string فارغ ' '
  • is_admin قيمتها true في حال كان العضو مديرا
$user = new User();

$user->name = $request['name'];
$user->email = $request['email'];
$user->password = bcrypt($request['password']);
// لكلمة المرور Hashing هي دالة مساعدة تقوم بعمل bcrypt

if( $request->has('bio') )
    $user->bio = $request['bio'];

$user->save();

 

قمنا بتعيين الاسم والبريد الالكتروني وكلمة المرور وجلبنا قيمهم من الـ Request وكذلك الحال بالنسبة لـ bio، لكن بما ان bio اختيارية، فقد لا يدخلها المستخدم، فوضعنا الشرط للتحقق من وجودها، فلو لم نضع الشرط ولم يدخلها المستخدم فسيتم ادخال القيمة null، والخاصية لا تقبل هذه القيمة كما اتفقنا مسبقا.

ثم قمنا بتخزين الـ Instance الجديد من المودل User في قاعدة البيانات، والطريقة مشابهة للتعديل، فمثلا لتعديل الاسم سنقوم بكتابة الكود الآتي ( على افتراض أننا جلبنا الـ user ووضعناه في المتغير user$ ):

$user->name = $request['name'];

$user->save();

مشكلة هذه الطريقة في الانشاء والتعديل أنها تحتاج لكتابة كود طويل في حال وجود كثير من الأعمدة في جدول users، لذلك توفر لارافيل طريقة أسهل لإدخال جميع الخصائص دفعة واحدة وهو ما يعرف بـ (Mass Assignment)، في الكود الآتي مثال على طريقة الانشاء وطريقة التعديل:

// الانشاء

$user = User::create($request->all())

// التعديل

$user->fill($request->all())

 

بهذه البساطة فقط يمكن الانشاء والتعديل دون الاضطرار لتعيين كل خاصية وحدها، فالميثود create والميثود fill تستقبل مصفوفة (array) يكون الـ key فيها عبارة عن اسم العمود، والـ value هي القيمة التي سيتم تعيينها.

 

في الغالب، عندما تستخدم طريقة الـ Mass Assignment، لن تعمل معك وستحصل على الخطأ MassAssignmentException، فما السبب في ذلك؟

هذا الخطأ يظهر لدواعٍ أمنية، فالـ Mass Assignment خطيرة بعض الشيء في حال استخدمتها بطريقة خاطئة، لذلك تتخذ لارافيل بعض الاجراءات الامنية وطرق الوقاية من الثغرة التي تسببها Mass Assignment، فتظهر لك هذا الـ Exception في حال لم تتخذ الإجراءات الوقائية المناسبة، لكن قبل الحديث عن الإجراءات الوقائية، يجب أن نفهم الثغرة بشكل واضح حتى نتمكن من حماية أنفسنا منها.

ما هي مشكلة الـ Mass Assignment

في المثال السابق، مررنا جميع محتويات الـ Request إلى دالة create و fill، وفي ذلك عدة فوائد، منها عدم الاضطرار إلى كتابة جميع القيم وتعديلها أو انشاؤها يدويا، كذلك التخلص من الشروط التي تتحقق من ادخال القيم الافتراضية، كالشرط الذي وضعناه لخاصية bio، فبشكل تلقائي، إن لم تحتوي الـ Request على bio فلن تكون موجودة في الـ array التي تم تمريرها وسنتخلص من هذه المشكلة.

لكن إدخال جميع محتويات الـ Request يؤدي إلى مشكلة كبيرة، فكما تعلم، يمكنك أن تقوم بإنشاء Http Request من خلال بعض الأدوات، أو من خلال التلاعب بالـ Form في HTML في المتصفح، فبإمكاني مثلا الدخول إلى موقعك، والتلاعب بالـ Request لتعديل قيمة الـ id مثلا، والمفروض أنني لا أمتلك صلاحيات لفعل ذلك، أو يمكنني التلاعب بالـ Request وأرسل القيمة true للمفتاح is_admin على الرغم من أنك في الأساس لم تضعه ضمن الـ Form، فهذا سيمكنني من الحصول على صلاحيات الإدارة في الموقع.

هذه مثلا أداة تمكنني من إنشاء Http Request، لاحظ كيف أستطيع تحديد الحقول التي أحتاجها، ومنها is_admin:

 

بالطبع هذه الـ Request لن تعمل على تطبيق لارافيل لأن لارافيل توفر طريقة سهلة لحماية الحقول التي لا تريد لأحد التلاعب بها أو تعديلها، وهذه الطريقة مفعلة بشكل افتراضي، لكنها تحتاج إلى بعض التعديل بحسب خصائص المستخدم لديك، لذلك ستحصل على الخطأ MassAssignmentException في حال لم تقم بما يلزم لحماية الـ Request من التلاعب.

 

حل المشكلة

لو قمت بفتح الـ Model الخاص بالمستخدم (User) الذي يأتي جاهزا مع لارافيل، ستجد مصفوفة باسم fillable$ وفيها بعض الخصائص:

 

لاحظ التعليق الذي يسبق المصفوفة " The attributes that are mass assignable " أي أن الخصائص الموجودة اسماؤها داخل المصفوفة هي الخصائص التي يُسمح بتعيينها باستخدام الـ Methods التي تدخل البيانات بشكل جماعي ( create و fill ) ففي حال قمت بعمل Request تحتوي فقط على name و email و password فإن الـ Request ستعمل دون مشاكل وستتم الإضافة أو التعديل باستخدام create او fill دون أن تواجه أي مشكلة، لكن لو كانت الـ Request تحتوي على الخصائص السابقة إضافة إلى bio مثلا أو is_admin، فستحصل على مشكلة MassAssignmentException لأن bio و is_admin غير موجودتان في مصفوفة fillable.

لذلك في حال أردت استخدام Mass Assignment Method كـ create أو fill، فإن عليك اضافة أي عنصر تريد أن يكون قابلا للتعديل إلى مصفوفة fillable،
في بعض الحالات تكون لديك الكثير من الأعمدة في الجدول، ومعظمها قابلة للتعديل، فبدلا من إضافتها كلها إلى fillable، قم بحذف المصفوفة واستخدم بدلا منها مصفوفة guarded$.
وهي تقوم بنفس العمل لكن بشكل عكسي، فبدلا من وضع الخصائص التي تريد أن تكون قابلة للتعديل، تضع الخصائص التي تريدها أن تكون محمية وغير قابلة للتعديل، فمثلا يمكن استخدام احدى المصفوفتين كالآتي على مثالنا السابق:

 

protected $fillable = [
    'name', 'email', 'bio', 'password'
];

// أو

protected $guarded = [
    'is_admin', 'id'
];

 

* تحذير: لا تقم باستخدام fillable$ و guarded$ في وقت واحد، قم باستخدام واحدة فقط.

 

كما ترى، بهذه الطريقة يمكن الاطمئنان إلى استخدام Mass Assignment دون الخوف من التلاعب بالـ Request، فلو تلاعب أي شخص بالـ Request وقام بمحاولة تعديل is_admin مثلا، فسيحصل على MassAssignmentException ولن يتم التعديل.

 

لكن قبل أن نختم، قد يتبادر إلى ذهنك تساؤل بسيط، هل بوضعنا لـ is_admin في مصفوفة guarded$ فلن نتمكن من تعديلها أبدا ؟

بل بإمكانك تعديل هذه القيم، لكن ليس باستخدام create أو fill، بل باستخدام الطريقة الأولى للتعديل، بل بامكانك استخدام fill، مع الطريقة الاولى بسهولة للسماح مثلا للـ Admin الخاص بالموقع بتعديل is_admin، ومنع باقي الأعضاء من تعديلها، شاهد هذا المثال:

$user->fill($request->except('is_admin'));

if(Auth::user()->is_admin && $request->has('is_admin')) {
    $user->is_admin = $request['admin'];
    $user->save();
}

 

لاحظ أننا في هذا المثال قمنا باستخدام الطريقتين في وقت واحد، أولا قمنا بعمل fill لجميع محتويات الـ Request باستثناء is_admin، ثم استخدمنا الطريقة الأولى للتعديل لكن بعد التحقق من أن المستخدم المسجل حاليا ( الذي يقوم بالتعديل ) يملك صلاحيات Admin، إضافة إلى وجود is_admin في الـ Request.

لكن في حال قمت أنا بالدخول إلى موقعك ( وأنا لست admin ) وحاولت التلاعب بالـ Request ومحاولة تعديل is_admin، فإن الشرط الأول سيمنعني، أما إن حاولت التلاعب بها لتعديل الـ id الخاص بي مثلا فلن يتم التعديل وسيظهر لي خطأ MassAssignmentException.

9
إعجاب
5269
مشاهدات
0
مشاركة
4
متابع
متميز
محتوى رهيب

التعليقات (2)

نجود:

شكرا لك  موضوع جداً مفيد . 

لكن لدي إستفسار : ذكرت ان لارافيل توفر طريقة سهلة لحماية الحقول التي لا تريد لأحد التلاعب بها أو تعديلها، عن طريق $fillable 

 لكن ما الحاجة او دواعي  إستخدام  $guarded إذ ان  $fillable   اسرع و اسهل بالاستخدام و  المستخدم العادي لا يمكنة التلاعب بالبيانات في حال إستخدامها . 

عمار الخوالدة:

في حال وجود جدول فيه الكثير من الاعمدة، 10 اعمدة مثلا، وكنت اريد منع الوصول الى is_admin فقط،
فبدلا من اضافة 9 اعمدة إلى fillable، اقوم باضافة is_admin الى guarded

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !