كيف تحمي بيانات المستخدمين من الإختراق عن طريق Salted Password Hashing


مستوى المقال: متوسط

بسم الله الرحمن الرحيم

كيف تحمي بيانات المستخدمين من الإختراق عن طريق

Salted Password Hashing

 

مقدمة:

إذا كنت مطور تطبيقات ففي الغالب أنك أنشأت في نظامك جزء خاص بإدارة حسابات المستخدمين (User Account Management). ما يهمنا هنا هو كيفية حماية بيانات المستخدمين في قاعدة البيانات. كما لا يخفى على الجميع أن الكثير من المواقع أو التطبيقات المشهورة تعرضت للإختراق وتم الحصول على بيانات المستخدمين. فما هي الطريقة المناسبة لحماية كلمات المرور أو بيانات المستخدمين البنكية. سنركز هنا على كلمات المرور والفكرة واحدة.

الطرق المختلفة لحفظ المرور في قاعدة البيانات:

-         حفظ كلمة المرور كنص واضح (Plaintext)

-         تشفير كلمة المرور ثم حفظ النص المشفر (Ciphertext)

-         استخدام الـ Hashing.

-         استخدام Salted Password Hashing

 

حفظ كلمة المرور كنص واضح (Plaintext)

هذه الطريقة هي أبسط الطرق وهي عملية حفظ كلمة المرور كما أدخلها المستخدم. مثلا أدخل 123 فهي تحفظ في قاعدة البيانات كما هي (123). فإذا نسيت كلمة المرور وطلبت من الموقع الذي أنت عضو فيه استعادة كلمة المرور فتم إرسال كلمة المرور كما هي على بريدك الالكتروني فهذا الموقع يستخدم هذه الطريقة أو الطريقة الثانية.

هذه الطريقة خطيرة جدا فأبسط عملية اطلاع على قاعدة البيانات يستطيع المخترق أن يعرف كلمة مرور أي مستخدم أو عضو مسجل. فأبتعد عن هذه الطريقة.

 

تشفير كلمة المرور ثم حفظ النص المشفر (Ciphertext)

كما نعرف جميعا أن التشفير في أبسط تعريف له هو عملية تحويل المعلومات من صيغتها الحقيقة المفهومة إلى صغية غير مفهومة. أو تحويلها من صيغها الحقيقية إلى صيغة أخرى.

ويتم ذلك عن طريق أحد أنواع التشفير التالية:

-         التشفير المتناظر/ المتماثل (Symmetric key / Private key)

حيث يتم التشفير (Encryption) وفك التشفير (Decryption) بنفس المفتاح.

-         التشفير الغير متناظر/ الغير متماثل (Asymmetric / Public key)

هنا يتم التشفير بالمفتاح العام (Public Key) ويتم فك التشفير بالمفتاح الخاص (Private Key) ويكون المفتاح العام معروف عند الجميع أما المفتاح الخاص فلا يعرفه إلا المالك نفسه. أما إذا عكسنا العملية فتم التشفير بالمفتاح الخاص وتم فك التشفير بالمفتاح العام (التحقق من المرسل) فهذا يسمى التوقيع الإلكتروني (Digital Signature).

 

موضوع التشفير وأنواعه لا تهمنا حاليا. ما يهمنا هنا هو عملية تشفير كلمة المرور ثم حفظها في قاعدة البيانات.

مثال: لنفرض أن خوارزمية التشفير هي الإزاحة (The Shift Cipher) وأن مفتاح التشفير هو الازاحة 3 خانات.

Encryption.png.5933075457a33f3cd4860d02ad55ec00.png

 

كما نلاحظ تتمت الإزاحة 3 خانات لكل حرف من النص الأصلي فمثلا 123 تصبح 456.

ففي هذه الطريقة يتم حفظ 456 في قاعدة البيانات ولو تكرر أن مستخدم أخر يستخدم كلمة المرور 123 فتصبح أيضا 456 مخزنة في قاعدة البيانات.

هذه الطريقة أصعب قليلا من الطريقة الأولى ولكن أيضا غير عملية فلو تم اكتشاف مفتاح التشفير فستصبح قاعدة البيانات واضحة ويمكن الحصول على كل كلمات المرور الخاصة بالمستخدمين.

يمكن تعقيد هذه الطريقة في حال أصبح لكل مستخدم مفتاح عام (Public key) ومفتاح خاص (Private Key) ولكن يصعب تنفيذ هذه الطريقة فأنت تحتاج لنظام لإنشاء مفاتيح للمستخدمين. فعند تسجيل المستخدم يتم تشفير كلمة المرور الخاصة به بالمفتاح العام. وعندما يقوم المستخدم بعملية تسجيل الدخول فيجب أن يكتب أو يرفق المفتاح الخاص به وهذا ينافي مبدأ التشفير الغير متناظر حيث يمكن معرفة المفتاح الخاص.

 

استخدام الـ Password Hashing

هذه الطريقة مستخدمة في العديد من المواقع وهي طريقة عملية ويصعب نوعاً ما أكتشاف كلمات المرور المخزنة في قاعدة البيانات عندما نستخدم هذه الطريقة. ولكن تظل طريقة لها العديد من الطرق التي تخترقها خصوصا في ظل تقدم التقنيات والأجهزة ذات المواصفات العالية.

ما هو الـ Hashing :

هو أحد علوم الـ (Cryptography) ولكن ليس تشفير (Encryption). فهي طريقة وحيدة الإتجاه (One way function) والتي تقوم بتحويل النص مهما كان طوله إلى نص ثابت (fixed-length). ولا يمكن إرجاع النص الناتج إلى النص الأصلي. أي لا يوجد فك للتشفير.

الفرق بين الـ Hashing  و التشفير (Encryption) ؟

عندما نشفر الكلمة 123 فتصبح 456 فإننا نستطيع فك التشفير من 456 لتعود إلى 123. ولكن في الـ Hashing عندما نحول 123 إلى 456 فلا يمكن إعادة 456 إلى النص الأصلي 123.

Hashing1.png.c66c4f29375bc668a5051ed2e6ba7622.png

 

كما نلاحظ في الشكل السابق أن النص الناتج من عملية الـ Hashing  نص ثابت الطول (10) خانات مهما أختلف طول النص في النص الأصلي في الجهة اليسار. أيضا نلاحظ أن 123 كانت عملية الـ Hashing  لها واحدة (Fdeom83nyU) مهما تكررت يكون الناتج واحد.

خوارزميات الـ Hashing:

يوجد العديد من خوارزميات الـ Hashing من أشهرها:

·         MD5   Message Digest Algorithm 5

·         SHA-1,SHA-2,SHA-3  Secure Hash Algorithm

·         BLAKE and BLAKE2

 

59306ce34ba31_Listofalg.thumb.png.40d35d3f3416bc9a23a4fccb1f443069.png

 

كيف يتم كسر كلمات المرور؟

في البداية يمكن إكتشاف كلمة المرور دون النظر إلى كيفية تخزينها في قاعدة البيانات هل هي بنص واضح أو مشفرة أو تم عمل Hashing  لها. وهناك خيارين ليتم كسر أو كشف كلمة المرور أو أي بيانات حساسة.

-         أن يتم الدخول على خوادم أي موقع أي أنه تم تجاوز عدة تحصينات سابقة وتم الحصول على قاعدة البيانات خصوصا جدول المستخدمين مثلا. فهنا إما أن تكون الكلمة مخزنة بشكل واضح وهذه بسيطة. أو تكون مشفرة أو Hashed Password. فهنا يتم التعامل معها بإحدى الطرق التي بالأسفل.

-         أو يكون المخترق لديه اسم المستخدم ويحاول الحصول على كلمة المرور. بمعنى أنه لا يمتلك قاعدة البيانات ولا يعرف الشكل الذي به تم حفظ كلمة المرور.

في الخيار الأول غالب يتم استخدام الأختراق الغير مباشر (Offline Attack)  بمعنى أن المخترق لديه كلمة مشفرة أو Hashed ويريد معرفة الكلمة الأصلية (Plaintext) فهنا يتم استخدام برامج تعتمد على الطرق التي بالأسفل لكي يحصل على الكلمة الأصلية دون التعامل المباشر مع الموقع الأصلي الذي تم اختراقه مسبقاً.

وغالبا في الخيار الثاني يتم استخدام الاختراق المباشر (Online Attack) حيث يكون لدى المخترق اسم المستخدم ويقوم بإستخدام برامج أيضا تعتمد على الطرق التي بالأسفل ويتم إرسال طلبات إلى الموقع المسجل لديه بيانات المستخدم.

كما عرفنا سابقا أن Hashing  وحيدة الإتجاه (One way function) أي لا يمكن إعادة النص الأصلي (Plaintext). ومع ذلك فإنه يمكن أن تكتشف الكلمة الأصلي ويتم ذلك بعدة طرق. وهذه الطرق إما أن تعتمد على مبدأ التخمين أو البحث في قاعدة بيانات كبيرة (جدول) تحتوي على العديد من كلمات المرور مع الـ hash  المقابل لها. ويتم ذلك ضمن خورزميات وعمليات حسابية طويلة ومقعدة لتضمن فعالية هذه الطرق.

ومن هذه الطرق:

1-    Dictionary Attack

هذه الطريقة تعتمد على مبدأ التخمين (Guessing ) حيث يكون هناك ملف يحتوي على قائمة كبيرة من الكلمات المستخدمة أو المشهورة بإنها تستخدم ككلمة مرور. أو تكون هذه القائمة متوقعة بالنسبة لشخص محدد مثل تاريخ الميلاد رقم الهاتف الخ. ويمكن استخدام هذه الطريقة من خلال (Online Attack) أو (Offline Attack) وفي الـ Offline Attack  تقوم بعض البرامج أثناء التنفيذ بعمل الـ Hash  المقابل لكل كلمة في الملف ويتم مقارنتها مع الHashed Password  التي يمتلكها المخترق وفي حال التطابق يتم معرفة كلمة المرور الأصلية.

 

مثال: لنفرض أن المخترق لديه هذا الحساب User@mail.com وكلمة المرور هي Car ( بالتأكيد لا يعرفها المخترق). ستتم هذه الطريقة حسب الملف الذي يمتلكه المخترق (الشكل بالأسفل)

table1.png.7c970d025be7dbb5388eb647b6f361b1.png

         ستتم تجربة كلمات الملف كالتالي

table2.png.3da2e58f2b6505e4a30bc26b0f88aa25.png

 

 

2-    Brute Force Attack

هذه الطريقة شبيهه بالطريقة السابقة حيث أنها تعتمد أيضا على التخمين ولكن التخمين بالاعتماد على عمليات حسابية حيث يتم تجربة كل الاحتمالات الممكنة مثلا تجربة كل الاحتمالات لكلمة مرور ذات طول 6 خانات عبارة عن أرقام أو أحرف. فهنا الموضوع لا يعتمد على جدول أو ملف إنما تجربة أثناء وقت التنفيذ. وهذه الطريقة تأخذ وقت طويل يزيد وينقص حسب طول كلمة المرور.

 

مثال : لنفرض أن المخترق لديه هذا الحساب User@mail.com وكلمة المرور هي  111333 ( بالتأكيد لا يعرفها المخترق).

تعمل هذه الطريقة بهذا الشكل:

table3.png.2631edc9d06ab26249224eb0d95a7b8b.png

سيتم تجربة كل الاحتمالات الممكنة حتى يصل إلى كلمة المرور. وقد تستمر إلى وقت طويل جداً حتى يتم إيجاد كلمة المرور. أيضا هنا تم استخدام (Online Attack) حيث أن المخترق يرسل للخادم في كل مرة اسم المستخدم مع احتمال لكلمة المرور بشكل متسلسل.

 

ويمكن استخدام الـ (Offline Attack) إذا كانت كلمة المرور محفوظة كـ Hash حيث يكون لدى المخترق الـ Hashed Password فيمكن اكتشاف كلمة المرور بنفس الطريقة ولكن يتم أولا عمل Hash   أُثناء التنفيذ لكل سلسلة من الحروف ثم مقارنتها مع الـ Hashed Password. لنفرض أن المخترق لديه هذه الكلمة (Hashed Password) التالية:

62b37844f7adeb0df6f180126327dca339fb7003

table4.png.4faa4265ff1a601392edb6b248acf8c7.png

 

 

3-    Lookup Tables
أو ما يسمى (Pre-Computed dictionary attack) لنعرف أولا معنى Pre-Computed أو

Precomputation  يقصد بها:

عملية تجهيز الجدول قبل وقت التنفيذ (Run Time) ففي الطريقة السابقة (Dictionary attack) لدينا قائمة كبيرة من الكلمات وفي الـ (Offline Attack) يتم عمل Hash  لكل كلمة من القائمة أثناء التنفيذ ومقارنتها مع الكلمة التي يتملتكها المخترق (Hashed Password) فهذه الطريقة تأخذ وقت كبير ولكن هنا يتم إعداد جدول أو قائمة يكون مقابل كل كلمة الـ Hash  الخاص بها حسب خوارزمية الـ Hashing.

وهذا الجدول يستفيد منه المخترق دائما دون عمل الـHash  مرة أخرى لكل كلمة أثناء التنفيذ. وتكون العملية فقط بحث عن الـ Hash  المطابقة للكلمة التي لدى المخترق ففي حالة التطابق يتكون كلمة المرور هي المقابلة للـ Hash

 

مثال: لنفرض أن لدى المخترق هذه الـ Hashed Password

e9989db5dabeea617f40c8dbfd07f5fb

ولديه هذا الجدول ( هذا الجدول تم إنشائه مسبقا Pre-Computed ) وليس أثناء وقت التنفيذ.

table5.png.93c18cf95d33fc4f2d0efcad29258b5e.png

 

فالعملية هي عملية بحث أو استعلام عادية للحصول على الـ Hash  الموجودة في الجدول والمطابقة لما لدى المخترق (e9989db5dabeea617f40c8dbfd07f5fb). وهنا تكون كلمة المرور الأصلية هي Car.

وطبعا هذه الطريقة تختصر الوقت لكن تعتمد كفائتها على كمية الكلمات الموجودة في الجدول.

 

يوجد بعض البرامج التي تقوم بإنشاء هذه الجداول حسب بعض المعطيات لدى المخترق ( أي لا يشترط أن يكون هناك قائمة جاهزة للكلمات المشهورة إنما يمكن إنشائها) من هذه المعطيات أن يتم إنشاء جدول (Lookup tables) للكلمات ذات الطول من 1 إلى 7 خانات وتكون عبارة عن أرقام فقط. ويمكن أن تكون أرقام أو حروف أو رموز. كلما زادت المعطيات زاد وقت إنشاء هذه الجداول وزادت مساحة التخزين.

 

4-    Rainbow Tables

هو نوع مخصص من Lookup table بنفس الفكرة والاعتماد على (precomputation ). ولكن تعمل تحت مبدأ (space/time trade-off) وهذا يعني زيادة مساحة التخزين في سبيل تقليل وقت التنفيذ أو وقت الحصول على النتيجة. ولكن وقت التنفيذ أقل من Brute Force Attack وكذلك مساحة التخزين أقل من Lookup table.

59306f908b70f_Rainbowtable.thumb.png.e0f13a4f8fa2842cf0c7ea27e5ad8ff6.png

 

الفرق بين الـ Rainbow table و Lookup table هو في طريقة إنشاء جدول الكلمات حيث يعتمد الـ Rainbow table على دالة الاختزال أو التقليص (Reduction function) والتي تعمل على تقليل حجم الجدول الناشئ حيث يتم استبعاد حفظ بعض كلمات المرور والـ Hash  المقابل لها. شرح التفاصيل الخاصة بالـ Rainbow table and Reduction function يحتاج موضوع مستقل. لكن سأذكر في النهاية بعض المواضيع التي تشرحها بشئ من التفصيل.

 

ملاحظة: تم توضيح الطرق السابقة في أبسط صورها وقد تم إضافة الكثير من التحسينات وتم استخدام خوارزميات معقدة تزيد من فعاليتها. ومعرفة هذه الطرق وتفاصيلها أحد الأسباب الرئيسية لحماية بياناتك أو حماية بيانات المستخدمين لديك  وتساعدك في فرض عدة قيود صلبة تجعل عملية الإختراق صعبة أو شبه مستحيلة.

 

استخدام الـ Salted Password Hashing

هذه الطريقة هي الهدف الرئيسي من هذا الموضوع. كما ذكرنا سابقاً أن عملية الـ Hashing  للكلمة 123 هو  Fdeom83nyU ( مثال فقط) فهنا كلما تكررت 123 ككلمة مرور لأي مستخدم ستكون النتيجة هي Fdeom83nyU وهذه النقطة تساعد المخترق على محاولة إستنتاج كلمة المرور من النص Fdeom83nyU بإستخدام الطرق السابقة أو غيرها. أما طريقة Salted Password تعمل على أن يكون الـ Hash للكلمة 123 مختلف في كل مرة وذلك بإضافة نص عشوائي للكلمة الأصلية 123 ثم عمل الـ Hashing  لها كما في الشكل التالي:

5930700017990_SaltedPassword.png.3cc491089054e618a99526ddd42ffb8f.png

 

نلاحظ في الشكل السابق إختلاف الناتج من عملية الـ Hashing في كلمة مرور حتى وإن كانت كلمة المرور واحدة.

الهدف من الكلمة Salt والتي تعنى ملح هو شيء مشابه للفائدة من الملح وهو تحسين الطعام أيضا هنا النص المضاف Salt يعني إضافة نص عشوائي إلى كلمة المرور الأصلية لتحسينها حتى تكون قوية يصعب كسرها أو إكتشافها.

طريقة عمل Salted Password Hashing:

أولا في مرحلة تسجيل البيانات حيث يقوم المستخدم بتعبئة بياناته على الموقع ثم يقوم بالحفظ. حيث تتم الخطوات التالية:

1-    يدخل المستخدم كلمة المرور لنفرض 123

2-    يقوم البرنامج بإنشاء نص مضاف (Salt) بشكل عشوائي لنفرض Nd29kd63w1po

3-    يتم دمج كلمة المرور مع النص العشوائي المضاف لتصبح 123Nd29kd63w1po

4-    يتم عمل Hash للنص 123Nd29kd63w1po وسيكون الناتج jdhsi3jf92 ( طبعا النص أطول من ذلك حسب خوارزمية الـ Hash  ).

5-    يتم حفظ الناتج jdhsi3jf92 و النص العشوائي المضاف Nd29kd63w1po في قاعدة البيانات مع بيانات المستخدم الأخرى كالبريد الالكتروني والاسم ..الخ.

 

ثانيا عند تسجيل الدخول يتم التالي:

1-    يُدخل المستخدم اسم المستخدم وكلمة المرور لنفرض 123

2-    يتم الاستعلام عن بيانات المستخدم بواسطة اسم المستخدم من قاعدة البيانات للحصول على الـ Salt  و الـ Hashed Password ونفرض أن Salt هو  Nd29kd63w1po والـ Hashed Password هي jdhsi3jf92.

3-    يتم دمج كلمة المرور المدخلة 123 مع النص Salt وهو Nd29kd63w1po لتصبح 123Nd29kd63w1po  ثم يتم عمل Hashing  لها ليصبح الناتج jdhsi3jf92

4-    ثم يتم مطابقة النص الناتج jdhsi3jf92 مع كلمة المرور المخزنة في قاعدة البيانات Hashed Password وهي jdhsi3jf92 وفي حال التطابق يتم تسجيل الدخول.

 

نصائح مهمة عند عمل Salted Password Hashing:

1-    عدم إعادة استخدام الـ Salt   نفسه مع كل كلمة مرور حيث يجب أن يكون عشوائي مع كل كلمة مرور.

2-    لا تستخدم Salt قصير يجب أن يكون نص عشوائي طويل على الأقل 16 حرف ( أيضا يمكنك الاعتماد على نوع خوارزمية الـ Hash لتحديد طول الـ Salt )

3-    استخدام خوارزميات الـ Hash  مثل (MD5,SHA1,SHA2,SHA3) في Salted Password Hashing يعتبر آمن ولكن هذه الخورزميات تعتبر قديمة والمخترقين لديهم عدة طرق لكسر كلمات المرور التي تم إنشائها بواسطتها وحتى مع وجود الـ Salt هي مجرد مسألة وقت ويمكن كسرها. لذلك يفضل عدم استخدامها بل استخدام (password-based key derivation function) مثل  PBKDF2, bcrypt, scrypt

4-    لا تقوم بإنشاء دالة خاصة تقوم بتوليد (Generate)  نص عشوائي Salt بل يفضل استخدام الدوال الجاهزة حسب لغة البرمجة كما في الشكل التالي:

5930705d55ed1_RandMethods.png.be782bc776035bc83bd32452eef53660.png

 

تطبيق: بلغة (ASP.Net (C#,VB)) مع قاعدة البيانات SQL Server

الفكرة:

سيتم إن شاء الله إنشاء تطبيق بسيط لعملية تسجيل أو إنشاء المستخدمين أيضا تسجيل الدخول. حيث يقوم المستخدم بتعبئة بياناته في صفحة التسجيل (Registration) وهي اسم المستخدم و الاسم كاملأ وكلمة المرور. ثم يقوم البرنامج بعمل Salted Password ( الـ Salt و Hashed Password  ) ثم يتم حفظ البيانات في قاعدة البيانات. وعندما تتم عملية التسجيل يمكن للمستخدم تسجيل الدخول عن طريق صفحة الدخول (Login) حيث يتم كتابة اسم المستخدم وكلمة المرور حسب الخطوات التي تم ذكرها بالأعلى في جزئية طريقة عمل  Salted Password Hashing.

التطبيق يعتمد على PBKDF2.

وهي خورازمية للـ (Password-Based Key Derivation Function) وليست للـ Hashing  بشكل مباشر مثل (MD5,SHA) والهدف منها استخدام إحدى خوارزميات الـ Hashing  المباشرة ولكن بطريقة تعمل على زيادة قوة الـ Hash  الناتج من كلمة المرور الأصلية بحيث يصعب كسر أو إكتشاف كلمة المرور من خلال Rainbow tables  أو Brute force attack.

ويتم ذلك من خلال  عدة نقاط  مثل طول الـ Salt  أيضا عدد الـ Iterations والذي يعمل على تحسين الـ Hash  الناتج من كلمة المرور الأصلية. لنفرض أن عدد الـ هو Iterations 30000 هنا تتم عملية التحسين 30000 مرة  وهذا مما يزيد في صعوبة إكتشاف كلمة المرور ولكن من الطبيعي يكون هناك بطئ أثناء وقت التفيذ حسب عدد الـ  Iterations. سأذكر إن شاء الله  في نهاية الموضوع بعض المصادر التي تشرح key derivation function و PBKDF2 وكذلك Key stretching.

 

الـ PBKDF2 في الـ .Net  يمكن استخدام الـ Rfc2898DeriveBytes والذي يعتمد على SHA-1.

 

قاعدة البيانات:

تم إنشاء جدول للمستخدمين يحتوى على عدة حقول كما في الشكل أدناه.

db_UsersAccounts.png.3ab1225aa2638fafad0a0e2448c201ae.png

CREATE TABLE UsersAccounts
(
	Username nvarchar(30)  PRIMARY KEY,
	FullName nvarchar(200) NOT NULL,
	Salt nvarchar(100) NOT NULL,
	HashedPassword nvarchar(100) NOT NULL
)

 

الفئات (Classes)

نحتاج لـ Two Classes  الأول خاص بقاعدة البيانات (SQLHelper) والثاني خاص بالـ Hashing  وهو (SaltedPassword) وهذا الأخير و لب هذا التطبيق.

 

فئة (Class) للتعامل مع قاعدة البيانات (SQLHelper)

يحتوي على دالتين

-         دالة الاستعلام حيث يتم إرسال جملة الإستعلام ويتم حفظ النتائج في جدول (DataTable)

-         دالة تنفيذ جمل الإضافة والتعديل والحذف ويتم من خلالها إرسال البيانات وحفظها في قاعدة البيانات.

SQLHelper Class

C#

using System;
using System.Data;
using System.Data.SqlClient;

public class SQLHelper
{
    public SQLHelper()
    {
        //
        // TODO: Add constructor logic here
        //
    }
    public static readonly string ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["connectionString1"].ConnectionString;

    // Select Queries
    public static DataTable ExecuteQuery(string commandText, CommandType commandType, SqlParameter[] parameters)
    {
        DataTable table = new DataTable();
        try
        {
            // ConnectionString كائن الإتصال مسؤول عن فتح وإغلاق الاتصال بقاعدة البيانات حسب المسار في 
            using (SqlConnection connection = new SqlConnection(ConnectionString))
            {
                // كائن الأوامر يتم من خلاله تجهيزة جملة الاستعلام والإضافة ليتم تنفيذها على قاعدة البيانات
                using (SqlCommand command = new SqlCommand(commandText, connection))
                {
                    // تحديد نوع كائن الأوامر
                    // Text: like (Select * from table ...)
                    // StoredProcedure: will be the name of StoredProcedure
                    command.CommandType = commandType;
                    // هي القيم التي يتم تمريرها مثل رقم الطالب والتي تكون مدخله من المستخدم parameters الـ 
                    if (parameters != null)
                        command.Parameters.AddRange(parameters);

                    connection.Open();
                    // DataSet مسؤول عن تعبئة النتائج في جدول أو
                    SqlDataAdapter adapter = new SqlDataAdapter(command);
                    adapter.Fill(table);
                }
            }

        }
        catch (Exception ex)
        {
            EventsLogger.SaveToLog(ex.Message);
            return null;
        }
        return table;
    }

    // Insert,Update and Delete
    public static bool ExecuteNonQuery(string commandText, CommandType commandType, SqlParameter[] parameters)
    {
        try
        {
            using (SqlConnection connection = new SqlConnection(ConnectionString))
            {
                using (SqlCommand command = new SqlCommand(commandText, connection))
                {
                    command.CommandType = commandType;
                    if (parameters != null)
                        command.Parameters.AddRange(parameters);

                    connection.Open();
                    if (command.ExecuteNonQuery() > 0)
                    {
                        return true;
                    }
                    else
                        return false;
                }
            }

        }
        catch (Exception ex)
        {
            EventsLogger.SaveToLog(ex.Message);
            return false;
        }

    }
}

VB.Net

Imports Microsoft.VisualBasic
Imports System.Data
Imports System.Data.SqlClient

Public Class SQLHelper

    Private Shared ConnectionString As String = System.Configuration.ConfigurationManager.ConnectionStrings("connectionString1").ConnectionString

    'Select Queries
    Public Shared Function ExecuteQuery(commandText As String, commandType As CommandType, parameters As SqlParameter()) As DataTable
        Dim table As New DataTable()
        Try
            Using connection As New SqlConnection(ConnectionString)
                Using command As New SqlCommand(commandText, connection)
                    command.CommandType = commandType
                    If parameters IsNot Nothing Then
                        command.Parameters.AddRange(parameters)
                    End If

                    connection.Open()
                    Dim adapter As New SqlDataAdapter(command)
                    adapter.Fill(table)
                End Using

            End Using
        Catch ex As Exception
            EventsLogger.SaveToLog(ex.Message)
            Return Nothing
        End Try
        Return table
    End Function


    ' Insert,Update and Delete
    Public Shared Function ExecuteNonQuery(commandText As String, commandType As CommandType, parameters As SqlParameter()) As Boolean
        Try
            Using connection As New SqlConnection(ConnectionString)
                Using command As New SqlCommand(commandText, connection)
                    command.CommandType = commandType
                    If parameters IsNot Nothing Then
                        command.Parameters.AddRange(parameters)
                    End If

                    connection.Open()
                    If command.ExecuteNonQuery() > 0 Then
                        Return True
                    Else
                        Return False
                    End If
                End Using

            End Using
        Catch ex As Exception
            EventsLogger.SaveToLog(ex.Message)
            Return False
        End Try

    End Function

End Class

 

فئة (Class) للتعامل مع الـ (Salted Password Hashing)
يحتوي على ثلاثة دوال:

-         دالة لإنشاء النص العشوائي المضاف Salt  .وهي GenerateSalt

-         دالة لعمل الـ Hash . وهي HashPasswordUsingPBKDF2

-         دالة للمطابقة عند تسجيل الدخول للتحقق من اسم المستخدم وكلمة المرور. وهي VerifyPassword

 

SaltedPassword Class

C#

using System;
using System.Security.Cryptography;

public class SaltedPassword
{
	public SaltedPassword()
	{
		//
		// TODO: Add constructor logic here
		//
	}
    private const int pbkdf2NoOfIterations = 30000;
    private const int hashSize = 32; 
    private const int saltSize = 32;

    /// <summary>
    /// Hashing دالة لإنشاء نص عشوائي ليتم إضافته إلى كلمة المرور قبل عملية الـ 
    /// </summary>
    /// <returns></returns>
    public static string GenerateSalt()
    {
        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
        byte[] salt = new byte[saltSize];
        rng.GetBytes(salt);
        return Convert.ToBase64String(salt);
    }
    /// <summary>
    /// Hash دالة تقوم بإنشاء كلمة المرور كـ 
    /// </summary>
    /// <param name="Password">كلمة المرور الأصلية التي أدخلها المستخدم</param>
    /// <param name="Salt"> النص المضاف التي تم إنشائه سابقا</param>
    /// <returns></returns>
    public static string HashPasswordUsingPBKDF2(string Password,string Salt)
    {
        byte[] bSalt = Convert.FromBase64String(Salt);
        Rfc2898DeriveBytes PBKDF2 = new Rfc2898DeriveBytes(Password, bSalt, pbkdf2NoOfIterations);
        byte[] key = PBKDF2.GetBytes(hashSize);
       
        return Convert.ToBase64String(key);

    }
  
    
    /// <summary>
    /// دالة للتحقق من كلمة المرور المدخلة أثناء تسجيل الدخول
    /// </summary>
    /// <param name="UserPassword">كلمة المرور المدخلة من قبل المستخدم أثناء تسجيل الدخول</param>
    /// <param name="Salt">النص المضاف المحفوظ في قاعدة البيانات</param>
    /// <param name="HashedPassword">Hash كلمة المرور التي تم حفظها في قاعدة البيانات كـ </param>
    /// <returns></returns>
    public static bool VerifyPassword(string UserPassword, string Salt,string HashedPassword)
    {
        string hash = HashPasswordUsingPBKDF2(UserPassword, Salt);
        // المقارنة بين القيمتين للتأكد من صحة كلمة المرور
        // New Hash with Hashed Password (from Database)
        if (String.Compare(hash, HashedPassword, false) == 0 )
            return true; // كلمة المرور صحيحة
        else
            return false;
       
    }


}

VB.Net

Imports Microsoft.VisualBasic
Imports System.Security.Cryptography

Public Class SaltedPassword

    Private Const pbkdf2NoOfIterations As Integer = 30000
    Private Const hashSize As Integer = 32
    Private Const saltSize As Integer = 32

    ''' <summary>
    ''' Hashing دالة لإنشاء نص عشوائي ليتم إضافته إلى كلمة المرور قبل عملية الـ 
    ''' </summary>
    ''' <returns></returns>
    Public Shared Function GenerateSalt() As String
        Dim rng As New RNGCryptoServiceProvider()
        Dim salt As Byte() = New Byte(saltSize - 1) {}
        rng.GetBytes(salt)
        Return Convert.ToBase64String(salt)
    End Function

    ''' <summary>
    ''' Hash دالة تقوم بإنشاء كلمة المرور كـ 
    ''' </summary>
    ''' <param name="Password">كلمة المرور الأصلية التي أدخلها المستخدم</param>
    ''' <param name="Salt"> النص المضاف التي تم إنشائه سابقا</param>
    ''' <returns></returns>
    Public Shared Function HashPasswordUsingPBKDF2(Password As String, Salt As String) As String
        Dim bSalt As Byte() = Convert.FromBase64String(Salt)
        Dim PBKDF2 As New Rfc2898DeriveBytes(Password, bSalt, pbkdf2NoOfIterations)
        Dim key As Byte() = PBKDF2.GetBytes(hashSize)

        Return Convert.ToBase64String(key)

    End Function


    ''' <summary>
    ''' دالة للتحقق من كلمة المرور المدخلة أثناء تسجيل الدخول
    ''' </summary>
    ''' <param name="UserPassword">كلمة المرور المدخلة من قبل المستخدم أثناء تسجيل الدخول</param>
    ''' <param name="Salt">النص المضاف المحفوظ في قاعدة البيانات</param>
    ''' <param name="HashedPassword">Hash كلمة المرور التي تم حفظها في قاعدة البيانات كـ </param>
    ''' <returns></returns>
    Public Shared Function VerifyPassword(UserPassword As String, Salt As String, HashedPassword As String) As Boolean
        Dim hash As String = HashPasswordUsingPBKDF2(UserPassword, Salt)
        ' المقارنة بين القيمتين للتأكد من صحة كلمة المرور
        ' New Hash with Hashed Password (from Database)
        If [String].Compare(hash, HashedPassword, False) = 0 Then
            Return True
        Else
            ' كلمة المرور صحيحة
            Return False
        End If

    End Function


End Class

 

صفحة التسجيل (Registration.aspx)

Registration.png.25fba163e0ef06a7ac7665db77b17bcd.png

 

Design

 <div class="center" dir="rtl">
        <table class="center">
            <tr>
                <td class="td">اسم المستخدم:</td>
                <td>
                    <input id="txtUsername" type="text" runat="server" class="textBox" maxlength="20" required="required" /></td>
            </tr>
            <tr>
                <td class="td">الاسم:</td>
                <td>
                    <input id="txtName" type="text" runat="server" class="textBox" maxlength="200" required="required" /></td>
            </tr>
            <tr>
                <td class="td">كلمة المرور:</td>
                <td>
                    <input id="txtPassword" type="password" runat="server" class="textBox" maxlength="20" required="required" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="كلمة المرور يجب أن تكون 8 خانات وخليط من الارقام والحروف الصغيرة والكبيرة والرموز" /></td>
            </tr>
            <tr>
                <td colspan="2">
                    <asp:Button ID="btnRegister" runat="server" Text="تسجيل" CssClass="button" OnClick="btnRegister_Click" /></td>
            </tr>
        </table>
        <br />
        <asp:Label ID="lbMessage" runat="server" CssClass="lableMsg"></asp:Label>
    </div>

 

 

الكود بداخل زر التسجيل (btnRegister)

C#

  protected void btnRegister_Click(object sender, EventArgs e)
    {
        try
        {
            string salt = SaltedPassword.GenerateSalt();
            string hashedPassword = SaltedPassword.HashPasswordUsingPBKDF2(txtPassword.Value, salt);

            // Save Into database

            // نستخدم هذه الطريقة للحماية من 
            // Sql Injection
            // command حيث نمرر القيم داخل 
            SqlParameter[] parameters = new SqlParameter[]{
                new SqlParameter("@Username",txtUsername.Value),
                new SqlParameter("@FullName",txtName.Value),
                new SqlParameter("@Salt",salt),
                new SqlParameter("@HashedPWD",hashedPassword),
                };

            // ويفضل استخدام الإجراءات المخزنة
            // Stored Procedures
            if (SQLHelper.ExecuteNonQuery("INSERT INTO UsersAccounts VALUES(@Username,@FullName,@Salt,@HashedPWD)", CommandType.Text, parameters))
                lbMessage.Text = "تم تسجيل البيانات بنجاح";
            else
                lbMessage.Text = "حدث خطأ أثناء تسجيل البيانات";
        }
        catch (Exception ex)
        {
            EventsLogger.SaveToLog(ex.Message);
            lbMessage.Text = "حدث خطأ أثناء تسجيل البيانات";
        }
    }

VB.Net

    Protected Sub btnRegister_Click(sender As Object, e As EventArgs)
        Try
            Dim salt As String = SaltedPassword.GenerateSalt()
            Dim hashedPassword As String = SaltedPassword.HashPasswordUsingPBKDF2(txtPassword.Value, salt)

            ' Save Into database

            ' نستخدم هذه الطريقة للحماية من 
            ' Sql Injection
            ' command حيث نمرر القيم داخل 
            Dim parameters As SqlParameter() = New SqlParameter() {New SqlParameter("@Username", txtUsername.Value), New SqlParameter("@FullName", txtName.Value), New SqlParameter("@Salt", salt), New SqlParameter("@HashedPWD", hashedPassword)}

            ' ويفضل استخدام الإجراءات المخزنة
            ' Stored Procedures
            If SQLHelper.ExecuteNonQuery("INSERT INTO UsersAccounts VALUES(@Username,@FullName,@Salt,@HashedPWD)", CommandType.Text, parameters) Then
                lbMessage.Text = "تم تسجيل البيانات بنجاح"
            Else
                lbMessage.Text = "حدث خطأ أثناء تسجيل البيانات"
            End If
        Catch ex As Exception
            EventsLogger.SaveToLog(ex.Message)
            lbMessage.Text = "حدث خطأ أثناء تسجيل البيانات"
        End Try

    End Sub

 


صفحة تسجيل الدخول (Login.aspx)

Login.png.43cc29ea238c3a639aa08f528837610a.png

 

Design

 <div class="center" dir="rtl">
        <table class="center">
            <tr>
                <td class="td">اسم المستخدم:</td>
                <td>
                    <input id="txtUsername" type="text" runat="server" class="textBox" maxlength="20" required="required" /></td>
            </tr>
            <tr>
                <td class="td">كلمة المرور:</td>
                <td>
                    <input id="txtPassword" type="password" runat="server" class="textBox" maxlength="20" required="required" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="كلمة المرور يجب أن تكون 8 خانات وخليط من الارقام والحروف الصغيرة والكبيرة والرموز" /></td>
            </tr>
            <tr>
                <td colspan="2">
                    <asp:Button ID="btnLogin" runat="server" Text="تسجيل دخول" CssClass="button" OnClick="btnLogin_Click"/></td>
            </tr>
        </table>
        <br />
        <asp:Label ID="lbMessage" runat="server" CssClass="lableMsg"></asp:Label>
    </div>

 

 

الكود بداخل زر تسجيل الدخول (btnLogin)

C#

 protected void btnLogin_Click(object sender, EventArgs e)
    {
        try
        {
            SqlParameter[] parameters = new SqlParameter[]{
                new SqlParameter("@Username",txtUsername.Value),
                };

            // الحصول أولا على معلومات المستخدم من خلال الاستعلام بإسم المستخدم
            DataTable userInfo =  SQLHelper.ExecuteQuery("SELECT * FROM UsersAccounts WHERE Username=@Username", CommandType.Text, parameters);
            if (userInfo == null || userInfo.Rows.Count <= 0)
                lbMessage.Text = "اسم المستخدم أو كلمة المرور غير صحيحة";
            else
            {
                string salt = userInfo.Rows[0]["Salt"].ToString();
                string hashedPassword = userInfo.Rows[0]["HashedPassword"].ToString();
                if (SaltedPassword.VerifyPassword(txtPassword.Value, salt, hashedPassword))
                    lbMessage.Text = "تم تسجيل الدخول بنجاح";
                else
                    lbMessage.Text = "اسم المستخدم أو كلمة المرور غير صحيحة";
            }
        }
        catch (Exception ex)
        {
            EventsLogger.SaveToLog(ex.Message);
            lbMessage.Text = "حدث خطأ أثناء تسجيل الدخول";
        }
    }

VB.Net

Protected Sub btnLogin_Click(sender As Object, e As EventArgs)
        Try
            Dim parameters As SqlParameter() = New SqlParameter() {New SqlParameter("@Username", txtUsername.Value)}

            ' الحصول أولا على معلومات المستخدم من خلال الاستعلام بإسم المستخدم
            Dim userInfo As DataTable = SQLHelper.ExecuteQuery("SELECT * FROM UsersAccounts WHERE Username=@Username", CommandType.Text, parameters)
            If userInfo Is Nothing OrElse userInfo.Rows.Count <= 0 Then
                lbMessage.Text = "اسم المستخدم أو كلمة المرور غير صحيحة"
            Else
                Dim salt As String = userInfo.Rows(0)("Salt").ToString()
                Dim hashedPassword As String = userInfo.Rows(0)("HashedPassword").ToString()
                If SaltedPassword.VerifyPassword(txtPassword.Value, salt, hashedPassword) Then
                    lbMessage.Text = "تم تسجيل الدخول بنجاح"
                Else
                    lbMessage.Text = "اسم المستخدم أو كلمة المرور غير صحيحة"
                End If
            End If
        Catch ex As Exception
            EventsLogger.SaveToLog(ex.Message)
            lbMessage.Text = "حدث خطأ أثناء تسجيل الدخول"
        End Try
    End Sub

 

ويكون شكل البيانات في الجدول كالتالي:

5930772450381_Databaseresult.png.89e982a470e4cfe62e551a1675de85d5.png

 

الخلاصة:

موضوع حماية بيانات المستخدمين متشعب كما نلاحظ في بعض النقاط السابقة. حاولت التركيز على الموضوع الرئيسي وهو استخدام الـ Salted Password Hashing وتم المرور على بعض المفاهيم الأخرى التي بعضها الحقيقة يحتاج إلى موضوع مستقل ولكن نسأل الله التيسر حتى استطيع شرح بعض هذه المفاهيم في مواضيع أخرى مثل Rainbow table  أو password-based key derivation function.

خلاصة هذه الموضوع التركيز على هذه النقاط.

-         عند تسجيل بيانات المستخدمين مثل كلمة المرور يجب أن لا تُقبل كلمات المرور القصيرة أو السهلة يفضل إجبار المستخدم على كلمة مرور معقدة.

-         استخدم الـ Salted Password Hashing.

-         العمل بالنقاط الموجودة في جزئية نصائح مهمة عند عمل  Salted Password Hashingالموجودة بالاعلى.

 

مراجع للإطلاع:

-      Salted Password Hashing - Doing it Right

https://www.codeproject.com/Articles/704865/Salted-Password-Hashing-Doing-it-Right

-      How Rainbow Tables work

http://kestas.kuliukas.com/RainbowTables

-      Rainbow Tables

https://stichintime.wordpress.com/2009/04/09/rainbow-tables-part-1-introduction

-      PBKDF2

https://en.wikipedia.org/wiki/PBKDF2

-      Key Derivation Function

https://en.wikipedia.org/wiki/Key_derivation_function

 

تمت بحمد الله.

 

التطبيق في المرفقات

Demo.rar

 

Talal Almutairi

https://twitter.com/talalsql

TALAL ALMUTAIRI

3


اراء المستخدمين




انشئ حساب جديد او قم بتسجيل دخولك لتتمكن من اضافه تعليق جديد

يجب ان تكون عضوا لدينا لتتمكن من التعليق

انشئ حساب جديد

سجل حسابك الجديد لدينا في الموقع بمنتهي السهولة .


سجل حساب جديد

تسجيل الدخول

هل تمتلك حساب بالفعل ؟ سجل دخولك من هنا.


سجل دخولك الان

Ads Belongs To This website

  • بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
     
    مقدمة
    سأتحدث في هذا الموضوع عن دورة حياة المشروع البرمجي ولن يكون موضوع واحد فقط وانما عبارة عن سلسلة مواضيع سأتحدث بها عن SPLC -وتعني Software Project Life Cycle- للمستقلين، وأعني بالمستقلين المبرمجين الذين يعملون بشكل مُستقل وليس مع فريق، والسبب الرئيسي لتحدثي عن هذا الموضوع هو انعدام جودة أغلب المشاريع البرمجية التي تتم بشكل فردي أو يتوقف تطويرها في مرحلةٍ ما بسبب عدم فهم الكثير من الأمور التي تتعلق بالمشروع البرمجي وقد تسبق كتابة الكود ايضاً.
     
    ملاحظة: SPLC هو مصطلح غير علمي والمصطلح المعروف SDLC -وتعني System Development Life Cycle- ولكن في هذه الدروس لن أتحدث عن دورة حياة تطوير المشروع بشكل خاص وانما عن دورة حياة المشروع البرمجي بشكل عام.
     
    جدول المصطلحات الهامة المستخدمة
    تمت الإشارة للمصطلحات الهامة باللون الأخضر
    المصطلح الوصف Ad Hoc Approach الطريقة الخاطئة التي يتبعها المستقلين حالياً. SDLC System Development Life Cycle دورة حياة تطوير البرمجيات. SPLC Software Project Life cycle دورة حياة المشروع البرمجي. وهو مصطلح غير علمي كما ذكرنا. Standard هي عبارة عن معايير ونماذج تحددها شركات كبيرة، وهذه النماذج مختبرة، ومجربة، وتم اثبات جدارتها، وتمنح الشركات المطبقة لهذه النماذج او المعايير بالشكل الصحيح شهادات اعتماد. Phases مراحل. Models نماذج. Build اصدار من البرنامج يكون مكتمل ويكون محدد باصدار؛ مثال: Buld 1، Build2...الخ. GUI Graphical User Interface واجهة المستخدم. AI Artificial intelligence الذكاء الاصطناعي.  
    اولاً ماهو المشروع البرمجي؟ وما الهدف منه اصلاً؟
    يوجد الكثير من الأشخاص الراغبين في تعلم البرمجة او لنكون دقيقين يرغبون في تعلم لغة برمجة، وذلك بهدف برمجة تطبيق او موقع على الانترنت، او حتى برنامج سطح مكتب، او لعبة...الخ ولكن للرغبة نوعان: نوع قد يكون بهدف، والأخر قد لا يكون بهدف؛ ومن هذا المنطلق نستطيع القول ان المشروع البرمجي هو هدف مبني على رغبة، وليس رغبة بحد ذاتها، وهذا الهدف لا يكون الا لحل مشكلة انسانية او تسريع اعمال او للقيام بالامور الحسابية بشكل فاعل وأسرع، أو حتى بالمجال الطبي أو بالمجال الاقتصادي، وبالغالب كل مجالات الحياة الانسانية، وبسبب هذه الأهداف أصبحت الرغبة بتسريع الأمور  وتسهيلها هاجس انساني بحت، حتى وصلنا اليوم الى الـ AI وتقدمنا فيه بشكل كبير. 
    خلاصة: اذاً فالمشروع البرمجي هو عبارة عن برنامج مبني على الحاسوب والهدف منه هو حل المشكلات الإنسانية او تسهيل الحياة او تسريع أداء المهام، أو حتى للمتعة.
     
    ثانياً ماهي المشكلة بالطريقة المتبعة حالياً من قبل المستقلين؟
    للأسف فالكثير من المبرمجين المستقلين حالياً يعملون على الكثير من المشاريع البرمجية سواءً مشاريع شخصية لمسعىً ربحي او مشاريع لأفراد وجميع هذه المشاريع تتم بشكل عشوائي وبطريقة نسميها علمياً Ad Hoc Approach.
    تعريف Ad Hoc Approach: هي الحلول المتبعة او الطرائق التي يتخذها المبرمج لإنجاز مشروعه، وهذه الحلول او الطرائق تكون غالباً مبنية على رؤية المبرمج -وليس بطرق مُنظمة بُنيت على الكثير من التجارب التي تم اثباتها-، وغالباً هذه الطرق التي يتخذها المبرمج غير قابلة للاستخدام في مشاريع او اهداف اخرى وذلك لعدم كفائتها فهي غالباً وإن لم تكن دائماً تُنتج مُخرجات ذات جودة رديئة تكون غير قابلة للإختبار، أو التطوير، او قد تكون غير قابلة للتعديل لاحقاً.
    وهذه العادة المؤسفة والمتبعة من قبل الكثير من المستقلين قد تطفىء الضوء عن كثير من المشاريع سواءً (مواقع الكترونية، تطبيقات للهواتف الذكية، أو ألعاب)  مُبكراً وذلك لرداءة جودتها كما ذكرنا سابقاً، وقد ينتهي المشروع الى قضايا قانونية يرفعها صاحب المشروع على المستقل المنفذ لهذا المشروع بسبب رداءة المنتج او عدم تطبيق طلب الطرف الأول بشكل صحيح.
     
    ثالثاً ماهي دورة حياة تطوير البرمجيات (SDLC):
    SDLC او System Development Life Cycle هي دورة حياة تطوير البرمجيات، وتعتبر هذه الدورة بكل مراحلها من ضمن مجالات هندسة البرمجيات، والتي تهدف لتفصيل الإجراءات والأساليب المتبعة لحوكمة وضبط عمل فريق تطوير البرمجيات، والتي أصبحت أساس من أساسيات بناء اي مشروع برمجي، وقد تختلف المراحل من مشروع الى أخر حسب حجم واحتياجات المشروع، وتطبيق جميع المراحل يجعل من المشروع ذا جودة عالية جداً، وموافق للمعايير القياسية (Standard).
     
    مراحل دورة حياة تطوير البرمجيات (SDLC):
    نظراً لأن الأخطاء التي يتم اكتشافها مؤخراً تكون مكلفة وصعبة المعالجة؛ لذا فتصور دورة الحياة يُسهل لنا التنبؤ بالأخطاء مُبكراً ويسمح للمبرمجين بالتركيز على جودة التطبيق والوقت المحدد لتنفيذ البرنامج، وايضاً وضع التكلفة المطلوبة في الاعتبار؛ وتتضمن دورة الحياة التي يمر بها تطوير البرمجيات المراحل الأتية:
    1 - Feasibility Study دراسة الجدوى.
    2 - Analysis التحليل.
    3 - Design التصميم.
    4 - Implementation التنفيذ.
    5 - Testing الاختبار.
    6 - Maintenance الصيانة.
    7 - Evaluation التقدير.
     

     
    المراحل الأساسية والمتعارف عليها:
    1 - Planning التخطيط.
    2 - Analysis التحليل.
    Requirement المتطلبات Definition المفاهيم 4 - Design التصميم.
    5 - Implementation التنفيذ.
    6 - Testing الاختبار.
    7 - Deployment التنصيب.
    8 - Maintenance الصيانة.
    9 - Evaluation التقدير.

     
    وهذه المراحل Phases هي مراحل مهمة جداً وأساسية في بناء المشاريع البرمجية، وكما ذكرنا سابقاً أن هذه المراحل قد لا تدخل جميعها في مرحلة بناء المشروع البرمجي وانما يتم انتقائها حسب الاحتياج، ولكن نؤكد لكم أن بعضاً من هذه المراحل الزامية ويجب ان تدخل في كُل مشروع برمجي احترافي وذو جودة عالية وقد تم تحديدها باللون الأحمر.
     
    ملاحظة: أساس من أساسيات بدأ او تنفيذ اي مشروع برمجي هو اختيار طريقة تنفيذ SDLC وفي الشطر التالي من هذا الموضوع سنتحدث عن هذه الطرائق او النماذج.
    ملاحظة: بشكل عام جميع المراحل أعلاه قد تكون تسلسلية فلا تبدأ مرحلة حتى تنتهي المرحلة التي تسبقها.
    ملاحظة: قد تختلف مسميات بعض المراحل فمثلاً Implementation يطلق عليها احيانا Development.
     
    أ. مرحلة التخطيط:  في هذه المرحلة يتم عمل دراسة لجدوى المشروع، والتواصل مع العميل لفهم المشروع وبناء صورة مبدئية له، والتحقق من الامكانيات لتنفيذ هذا المشروع، فهي تعتبر مرحلة اتخاذ قرار مبدئي.
    ب. التحليل: تعتبر من أهم مراحل دورة حياة المشروع البرمجي، وهي البادئة الفعلية لل SDLC حيث يتم في هذه المرحلة الاقتراب أكثر من العملاء لفهم المشروع، وتحديد مُتطلباته Requirement، وتحديد المفاهيم والأهداف وايطار المشكلة، وحذف المفاهيم الغير منطقية واستبيان وفهم المشروع بشكل أكبر، فهي تعتبر خطوة تحديد الطلب، وعلى أساسها يتم تنفيذ المراحل التالية.
    Requirement المتطلبات: هي رغبات العميل، وتطلعاته وهي مهمة جداً في فهم المشروع وتحديد مشكلة العميل وماذا يريد بالضبط.
    ج. مرحلة التصميم: هي المرحلة التي يتم من خلالها عمل التصميم المبدئي للمشروع، ولا نعني بالتصميم ال GUI وانما مخططات المشروع تماماً مثل المهندس المعماري عندما يرسم مخططات المنزل قبل بنائه، فهذه الخطوة اساسية جداً ولاحقاً سأشرح التصميمات التي يحتاج المستقل ان يعملها لبناء مشروعه بشكل احترافي.
    د. مرحلة الاختبار: في هذه المرحلة يتم اختبار المشروع، والتحقق من أنه يعمل بشكل جيد ويوجد نوعين من الاختبارات الأساسية Black Box Testing و White Box Testing وسنتعرف على هذه الانواع لاحقاً.
    ك. التنصيب: وهي عملية نقل المشروع من بيئة التطوير الى بيئة التشغيل في مكان العميل.
    ص. الصيانة: أهم مراحل SDLC وقد تكون الأكثر ثمناً والأطول في المدة.
     
    رابعاً كيف ابدأ المشروع البرمجي؟
    هناك الكثير من الطرق أو النماذج التي يتبعها اغلب المستقلين المحترفين في المجال البرمجي، وتكون من بداية المشروع حتى مرحلة التسليم ثم التطوير، وهذه النماذج لا استطيع القول انها جيدة ولكنها مستخدمة، وهي أفضل بكثير من Ad Hoc،  ونطلق على هذه النماذج مسمى Models، وسأذكر في هذا القسم البعض من هذه الطرائق.
    ملاحظة: سأطلق على Models لاحقاً بالطرائق أو النماذج.
    خلاصة: مما تم ذكره سابقاً نستطيع القول أنه لا توجد طريقة واحدة لبدء المشروع البرمجي، وانما هنالك الكثير من الطرق المعترف بها والمجربة تطبيقياً، وجميع هذه الطرق لم تجرب في مشروع واحد وانما لها تاريخ من الفشل والتطوير، ثم الفشل والتطوير، ...، وسلسلة طويلة من قصص فشل وتطوير حتى توصلنا الى Standard عام لهذه الطرائق، وليومنا هذا مازالت اغلب الطرق قيد التطوير. والهدف من هذه الطرق او النماذج هو تطبيق SDLC بالشكل الأمثل.
    ومن هذه النماذج (الطرق) نذكر:
    1 - Code and Fix: وهذه الطريقة مستخدمة كثيراً من قبل أغلب المستقلين وهي سيئة جداً في حال تم تطبيقها لمشروع كبير او متوسط الحجم، وهي بكل بساطة نبدأ بكتابة الشفرة البرمجية حتى ننتهي، في حال ظهور مشاكل نصلحها او حتى نحصل على رضا المستخدم، ثم نبدأ بكتابة بقية الشفرة حتى ننتهي، وفي حال ظهور مشاكل نصلحها او حتى نحصل على رضا المستخدم، ونستمر بهذه الدورة حتى يكتمل المشروع.
    لاحظ في هذا النموذج استخدمنا مرحلة ال التخطيط، والتنفيذ فقط.

     
    وهذه الطريقة مناسبة جداً للمشاريع الصغيرة والتي لا تكون حساسة، مثل المواقع الشخصية الصغيرة، أو التطبيقات المصغرة جداً مثل تطبيقات عرض الأخبار أو الألعاب الصغيرة.
     
    2 - Incremental: هذه الطريقة تستخدم كثيراً في التطبيقات العامة التي يتم تطويرها وبيعها للمستخدمين، وهذه الطريقة جيدة ولكن لها سلبياتها للأسف، وهي بكل بساطة أن نعمل اصدارات للمشروع البرمجي وكل اصدار يسمى Build وكل Build أثناء تنفيذه يتم تطبيق جميع مراحل SDLC تقريباً  بشكل تكراري Itrative، وسنتحدث عن هذه المراحل في موضوع أخر ان شاء الله.
     
    3 - Agail: هذه الطريقة هي دمج مابين Incremental و Itrative وتركز على تنفيذ المشروع بالشكل الذي يحقق رضا العملاء، عن طريق التسليم السريع للمنتج، وهذه الطريقة تعتبر سلسة جداً من ناحية الاستخدام وهي من الطرق التي يستطيع استخدامها المستقلين.

     
    ملاحظة: هذه بعض النماذج التي احببت ذكرها، وهي نماذج جيدة جداً، ويستطيع المستقل استخدامها بسلاسة نوعاً ما.
    خلاصة: اذاً اجابتاً على السؤال: كيف ابدأ المشروع البرمجي؟ الاجابة تكمن في اختيار الطريقة او النموذج الصحيح؛ واختيار نموذج او طريقة العمل ليس سهل وانما يتطلب خبرة عالية وفهم عميق للمشروع، ولكن لا تقلق حتى لو كنت مبتدأ فمعرفتك البسيطة بهذه النماذج سوف يساعدك كثيراً على اختيار الصحيح -تقريباً- منها.  على كُل حال فهذه النماذج قد لا تكون واضحة للبعض ولكن لاحقاً في موضوع أخر سنشرح هذا النماذج بشكل أفضل ونشرح طرق الاختيار.
     
    خامساً تحليل وتلخيص المعلومات:
    اذا لنربط المعلومات ونوضح الأفكار ونضمن فهمك للموضوع بشكل جيد سنضع نقاط وسيتم تلخيص كل نقطة.
    ماهي المشكلة مع الطريقة التي يتبعها المستقلين في مشاريعهم البرمجية. ماهي المخاطر الممكنة او المتوقعة التي تسببها الطرق الخاطئة في تنفيذ المشاريع البرمجية. كيف ابدأ ببناء مشروع برمجي ذو جودة عالية. SDLC و SDLC Models. أ. ماهي المشكلة مع الطريقة التي يتبعها المستقلين في مشاريعهم البرمجية؟: المشكلة باختصار تكمن في جودة المنتج، فتنفيذ المشاريع البرمجية لا يكون فقط بكتابة الكود، وانما التخطيط الصحيح والتصميم الأمثل، واتباع الطريقة الأكثر فعالية لضمان بناء المشروع بجودة عالية، وسهولة اختباره، وامكانية صيانته وتطويره لاحقاً.
    ب. ماهي المخاطر الممكنة أو المتوقعة التي تسببها الطرق الخاطئة في تنفيذ المشاريع البرمجية؟:  الجودة، صعوبة الاختبار والتحقق، صعوبة الصيانة، و القضايا القانونية.
    ج. كيف ابدأ ببناء مشروع برمجي ذو جودة عالية؟: لبناء مشروع برمجي ذو جودة عالية، يجب ان تبدأ اولاً بفهم المشروع بشكل صحيح، ثم كتابة متطلبات المشروع، ثم عمل التصميمات التوضيحية وتأكيد فهم المشروع، ثم التنفيذ، ثم الاختبار، ولاحقاً الصيانة، وكل ماتم ذكره سابقاً من مراحل SDLC والتي يتم تنفيذها بشكل فعال من خلال SDLC Models.
    د. SDLC و SDLC Models: دورة حياة تطوير المشروع البرمجي SDLC هي مجموعة مراحل يجب تنفيذها لبناء المشروع البرمجي بشكل صحيح، وطريقة تنفيذ هذه المراحل يكمن من خلال نماذج دورة حياة تطوير المشروع البرمجي SDLC Models.
     
    خاتمة
    من المتوقع كثيراً خصوصاً مع كثرة المُصطلحات والأفكار أن يكون فهمك لأهمية SPLC غير كامل وقد يكون مُبهم، لذلك لا تقلق ففي المواضيع القادمة والتي ستكون تحت عنوان دورة حياة تطوير البرمجيات: X -حيث أن X يمثل العنوان الفرعي- سنتحدث بالتفصيل عن مراحل SDLC، وعن الأدوات، والنماذج التي سوف تساعدك كمستقل على ادارة مشروعك البرمجي بشكل احترافي لتضمن جودته. 
     
    الموضوع التالي دورة حياة تطوير البرمجيات: المرحلة الأولى التخطيط
     
    هذا وصلى الله وسلم على نبينا محمد وعلى آله وصحبه اجمعين
    مستوى المقال: مبتدئ

    بواسطه Nawaf Alshareef , في

  • رغم انه المفترض في هذا الموضوع اتحدث عن طرق الاستخدام الخاطئ للـ Segue وتركيزه ينصب في شرح الـ Unwind Segue الا اني سوف أقوم بشرح الامرين لجعل الموضوع اشمل
     
     
    ما هو الـ Segue ؟
    الـ Segue هو عملية الانتقال بين صفحة (View Controller) الى اخر ويتم عن طريق ربط زر او Action موجود في الصفحة الاولى الى الصفحة الأخرى
     
    اهم قاعده هنا الانتقال يكون من صفحة 1 الى صفحة 2 ولا يحصل انتقال بشكل عكسي أيضا !
    هذا من اكثر الاخطاء شيوعاً عند المطورين الجدد !
    لفهم الـ Segue بشكل صحيح سوف اشرحه على مراحل او طرق بالتدرج
     
    الطريقة الاولى :
    في هذه الطريقة فقط نقوم بالربط بين صفحة وأخرى وتستخدم الطريقة هذه بشكل نادر فقط لغرض عرض صفحة أخرى دون الحاجة لاستخدام اكواد او نقل معلومات من صفحة الاولى الى الثانية
     
    والطريقة تكمن في التالي :

     
    أولا : نقوم اول بإضافة 2 من View Controller
     
    ثانيا : نقوم بإضافة Button في صفحة 1
     
    ثالثا : نقوم بالضغط الـ Button + زر control ونسحب الـ Button الى الـ View Controller الاخر
     
    رابعا : نختار Show
     
    من امثله استخدام الطريقة هذه اذا اردت أن تعرض صفحة اتصل بنا او عن التطبيق, في هذه الصفحات فقط تعرض معلومات ولا تحتاج الا نقل معلومة من صفحة الى أخرى او تحتاج أن تقوم ببعض الامور قبل عملية الانتقال, لذا استخدام الطريقة هذه طريقة نادره
     
    الطريقة الثانية :
     
    الطريقة هذه تعتمد على موضوع الانتقال للصفحة الثانية بعد تنفيذ امر معين وبدون الحاجة الى نقل معلومات من الصفحه 1  الى الصفحة 2
     
    لكي تفهم الفكرة بشكل صحيح ، لنفترض بأنك تعمل على صفحة "التسجيل حساب جديد" راح تحتاج تكتب التالي في Function الـ Button
    تتأكد بأن المستخدم كتب جميع الحقول من اسم المستخدم والايميل والباسورد وإعادة كتابة الباسورد تتأكد بأن هناك تطابق في حقل باسورد مع حقل إعادة كتابة الباسورد تتأكد من تلبية شروط كتابة الباسورد على سبيل المثال بأن تم كتابة ٨ احرف تتصل بالسيرفر في حال تم الاتصال وحفظ بيانات المستخدم بنجاح هنا يتم الانتقال الي الصفحة الثانية !  
    هل لاحظت الفرق ؟
     
    في الطريقة الاولى الانتقال مباشرة الى الصفحة الثانية ولكن في هذه الطريقة هناك أمور كثيره تحدث بمجرد ضغط المستخدم على الـ Button وتتم الانتقال اثناء حدوث امراُ معيناً وليس بشكل مباشر !
    والطريقة تكمن في التالي :
     
    نفس خطوات الطريقة الاولى بالضبط ولكن هنا يتم إضافة مُعرف (Identifier)
     
    لاحظ الصورة التالية :

     
    وفي داخل Function الـ Button
    نكتب التالي :

     
            performSegue(withIdentifier: "toView2", sender: nil)  
    لاحظ في السطر السابق كتبنا نفس المُعرف بالضبط الذي كتبناه في حقل الـ Identifier في الـ Storyboard
     
    وأقصد هنا بالضبط  ، المسافة تفرق ، الحرف الكبتل والسمول أيضا يفرق ! بما يعني الأفضل أن تقوم بنسخه ومن ثم لصقه بدلاً من كتابته .
     
    قد تتسأل ما الفائده من المُعرف ؟
     
    المُعرف هو المسؤول عن توجهيه الـ View Controller الى  الـ View Controller  الصحيح.
     
    في تطوير التطبيقات سوف تحتاج الي عملية انتقال مختلفة من نفس الـ View Controller قد يحتوي تطبيقك مثلا على زرين كل زر يوجه الى صفحه مختلفة فهنا المُعرف يجعل الـ Xcode يوجهك الى الصفحة الصحيحة.
     
    الطريقة الثالثة :
    في هذه الطريقة اذا اردت بأن تقوم بنقل معلومات من الصفحة 1 الى صفحة 2
    الطريقة هذه أيضا نفس الطريقة السابقة من حيث انك تعطي مُعرف (Identifier) الفرق بأنه سوف تحتاج الى استخدام Function معين يقوم بوظيفة نقل المعلومات اثناء عملية الـ Segue.
    هذا هو الـ Functionn الذي سوف تحتاج الى استخدامه:
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {         }  
    اين تضعه ؟
    تضعه في الصفحة الحالية, فانت تريد نقل معلومة من صفحة 1 الى صفحة 2 فتضعه في صفحة 1
    هنا تحتاج تعرف معلومة :
     
    لنقل بيانات من صفحة الى اخرى سوف تحتاج الي متغير في الصفحة الأخرى لكن تحفظها به ولكي تتضح الصورة الان في صفحة 1 تريد نقل معلومة معينه الى صفحة 2.
     
    المعلومة هذه عباره عن String اذا سوف تقوم بإنشاء متغير من نوع String في صفحة 2 ومن ثم في داخل الـ Function السابق تستدعي المتغير وتحفظ القيمة الموجودة في صفحة1  الي المتغير في صفحة 2.
     
    نعود للشرح
     
    الان في الـ View Controller الاول نضيف Textfield فوق الـ Button ونربطه بملف الأكواد.
     
     
    ومن ثم في الـ View Controller الثاني نضيف Lable لكن قبل ذلك نضيف Class من نوع View Controller ومن ثم نحدد الـ View controller  الثاني في الـ Stroryboard
    ونختار الـ Class الذي أنشأناه بعدها نربط الـ Label بملف الاكواد
     
     
    اصبح لدينا في الـ View Controller الاول فقط Button و Textfield وفي الـ View Controller الثاني لدينا Label
     
    قبل العودة الى Function الذي اضفناه في ملف اكواد الـ View Controller الاول يتوجب علينا إضافة متغير من نوع String لذا نقوم باضافته
     
    فيصبح شكل الـ StroyBoard بالشكل التالي :

     
    ملف اكواد الـ View Controller الاول بالشكل التالي :

     
    import UIKit class ViewController: UIViewController {   @IBOutlet weak var textField: UITextField!    override func viewDidLoad() {     }             override func prepare(for segue: UIStoryboardSegue, sender: Any?) {       } }  
    ملف اكواد الـ View Controller الثاني بالشكل التالي :
     

     
    import UIKit class ViewController2: UIViewController { var  text:String?   @IBOutlet weak var label: UILabel!   override func viewDidLoad() { super.viewDidLoad()     label.text = text            } }  
    كما تلاحظ في الـ viewDidLoad اضفت السطر التالي
    label.text = text لجعل النص الذي سوف نجلبه من View Controller الاول يتم طباعته في View Controller الثاني
    نعودة الان الى Function
     
    الان نعود لملف اكواد الـ View Controller الاول
     
    ونقوم بكتابة التالي
        override func prepare(for segue: UIStoryboardSegue, sender: Any?){ if segue.identifier == "toView2" { if let vc = segue.destination as? ViewController2 {   vc.text = textField.text!      }       }   }  
    لاحظ التالي اول سطر
    segue.identifier  
    نكتب فيه مُعرف الـ segue كما قمنا بكتابته في الـ Stroyboard
     
    السطر الثاني نقوم بكتابة اسمك الكلاس الـ View Controller الثاني في حالتي اسمه ViewController2
    السطر الثالث نستدعي المتغير الذي أنشأناه في الـ View Controller الثاني ونربطه بمحتوى الـ Textfield لذا اثناء عملية الـ Segue سوف ينتقل النص الذي سوف نكتبه في حقل الـ Textfield الى الـ String الموجود في الصفحة الثانية ومن ثم في الـ View Controller الثاني سوف يتم طباعة الـ String في الـ Label
     
    نقوم بتشغيل التطبيق ونرى النتيجة :

     
    هذه الطريقة الصحيحة لنقل البيانات من View Controller الى اخر وهيا الطريقة الأكثر استخداماً
     
    الطريقة الرابعة :
     
    الطريقة هذه هيا عباره عن دمج طريقة الثانية مع الثالثة
    هناك أوقات تحتاج الى الانتقال الي View Controller اخر بعد حدث معين وفي نفس الوقت تريد فيها نقل البيانات الى الـ View Controller اخر
     
    في هذه اللحظة سوف تحتاج الى الطرقتين بحيث تستخدم
            performSegue(withIdentifier: "", sender: nil)  
    وفي نفس الوقت تستخدم
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {    }  
    الـ Xcode ذكي بما فيه الكفايه
     
    اثناء تنفيذ سطر
            performSegue(withIdentifier: "", sender: nil)  
    سوف يلاحظ وجود Function
    override func prepare(for segue: UIStoryboardSegue, sender: Any?)  
    وبالتالي سوف ينتقل اليه أولا قبل تنفيذ الـ Segue وفقط للمعلومية prepare تعني تجهيز وبالتالي الـ Function يستعد لعملية الـ segue فيتم تنفيذ ما بداخله أولا !
    الان انتهينا من جزء الـ Segue

    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     
     
    حان الوقت الانتقال الي موضوع الـ Unwind Segue وهو الموضوع الذي اردت التحدث عنه !
    كل شخص جديد يتعلم برمجة تطبيقات الـ iOS وتحديدا عند تعلمه الـ Segue وطريقة نقل البيانات من صفحة الى أخرى يخطئه هذا الخطأ !!
    اغلبية المبرمجين الجدد يخطئوا نفس الخطأ !
     
    ماهو الخطأ الذي يقع به الكثيرون ؟
     
    استخدام الـ Segue عند الرغبة في الرجوع الى الصفحة السابقة !
    في هذه النقطة سوف أوضح لك الخطأ ولماذا يعتبر مصيبه عند عمله !!
    افتراضاً نملك 4 صفحات و ٣ Button كل Button متصل بـ Segue ينقلك الى الصفحه الأخرى
     
    كما في الصورة التالية :
     

     
    قد تتسأل ماهي المشكلة في الصورة السابقة ؟
     
    الحقيقة هيا لحد الان لا توجد مشكلة !
    لكن في الصورة التالية تكمن المشكلة !
    المشكلة تكمن هنا عندما ينشأ المبرمج زر للرجوع الى الصفحة الرئيسية
     
    كما في الصورة التالية :
     

     
     
    ماذا فعل هنا ؟
     
    قام بربط زر الرجوع الى صفحة رقم 0 عن طريق الـ Segue !!
    هذا هو الخطأ الذي اردت التحدث عنه !
     
    هناك 3 مشاكل تسببه هذه المشكلة!
     
    أولا : كثرة الـ Segue ذهابا وعوده تسبب مشكله لذا المطور في انه لا يعلم هذا الـ Segue مرتبط بأي View Controller ؟
    ثانيا : مشاكل في الأداء واستهلاك موارد الجهاز بما يسبب بطئه عند استخدام التطبيق !!
    ثالثا : مشاكل في Auto Layout ! ، من ضمن المشاكل التي تسببها ، مشاكل في الـ Auto Layout من الامور التي سوف تلاحظها رغم وضعك القيود بشكل صحيح الا انه لايزال يظهر خطأ في القيود !
     
     
    لستُ مقتنعاً ؟
     
    سوف أقوم الان بتشغيل التطبيق واستخدامه ذهاباً وعودة
     
    شاهد الصورة التالية :

     
    الان قم بالتالي ، لا تقوم بإيقاف تشغيل المشروع
     
    واذهب لهذه الخانة :

     
    وحرك باستخدام الماوس لملاحظة هيكل المشروع
     
    لاحظ الصورة التالية :

     
    نقوم الان بعمل Debug لهيكل المشروع من ناحية واجهة المستخدم
     
    هل لاحظت كثرت الطبقات ؟
     
    المفترض أن تظهر فقط 5 صفحات 4 صفحات خاصة بالـ View Controller والصفحه 5 للزر الموجود في اخر صفحة بحكم اني قمت بالتوقف عندها ! ولكن الذي يظهر هو 13 طبقة !
     
    ما السبب ؟؟
     
    السبب هنا بسبب طريقة ربط الـ Segue
     
    ذكرت في بداية الموضوع الـ Segue يذهب من الصفحة 1 الى الصفحة 2 وليس العكس !
    عند عمل العكس هذا ما سوف يحدث !
     
    السبب عند عمل هذا الخطأ يقوم النظام على انشاء الصفحة مره أخرى وأخرى وأخرى كل مره يتم الضغط على زر الرجوع يتم انشاء الصفحة مره أخرى والنتيجة هي استهلاك موارد الجهاز !!
    الان قبل أن اشرح الطريقة سوف انفذ الطريقة الصحيحة ومن ثم سوف اشرحها بعد تغير الطريقة الى الطريقة الصحيحة
     
    لاحظ الصورة التالية في الـ Stroyboard:

     
    هل لاحظت ؟
    لايوجد Segue ، للعودة لصفحة الرئيسية !
     
    الان لاحظ الصورة التالية :

     
    ومن ثم لاحظ هيكل المشروع :
     

     
    اذا عملت مقارنه بين الصورتين هذه والصورتين السابقة
     
    سوف تلاحظ امرين
     
    أولاً : عند الضغط على زر العودة الصفحة الحالية تنزل الى الأسفل وتظهر الصفحة الرئيسية ! ، في حين عند عمل الطريقة الخاطئة تظهر الصفحة الرئيسية من الأسفل الى الأعلى !!
     
    ثانياً : هيكل المشروع اصبح 5 فقط ! لم يتغير ولم يزداد عدده !
     
    لماذا ؟
     
    عند عمل الطريقة الخاطئة ، تقوم على انشاء الصفحة من جديد في كل مره فكل ما زاد وقت استخدامك التطبيق زادت عدد الصفحات وزاد استهلاك موارد الجهاز في حين عند عمل الطريقة الصحيحة ، تقوم على اغلاق الصفحة الحالية والعودة للصفحة التي تريدها ، وبالتالي لا يوجد أي زياده !
     
    بعد فهم الفكرة واتضاح الفرق
     
    نعود الى شرح الطريقة الصحيحة :
     
    لكن قبلها اريد توضيح نقطه معينه اذا اردا الرجوع الصفحة السابقة السابقة هناك 3 طرق
     
    الطريقة الاولى : استخدم هذا السطر بداخل اقواس الـ Button
    dismiss(animated: true, completion: nil)  
    الزر ذا سوف يرجعك الى الصفحة السابقة فقط بما يعني اذا كنت في صفحة 3 سوف يرجعك الى صفحة 2 لا يمكنك العودة الى صفحة 0 او أي صفحة أخرى
     
    الطريقة الثانية : عند استخدام الـ Navigation Bar
    سوف يظهر زر العودة للصفحة السابقة بشكل تلقائي وأيضا مثل الطريقة الاولى سوف تعود فقط الى صفحة السابقة يمكن ملاحظتها في جميع تطبيقات النظام مثال تطبيق الاعدادات عندما تذهب لقسم عام او General سوف تلاحظ وجود زر في الأعلى يرجعك الى صفحة الرئيسية للإعدادات ، هذه تتم بشكل تلقائي بدون تدخل منك
     
    معلومة :
     
    لاحظ الصورة التالي :
     

     
     
    سوف تلاحظ عند استخدام الـ Navigation Bar واختيار نوع Show سوف تكون عملية الانتقال الـ Segue من اليمين الى اليسار والعوده سوف تكون العكس في حين اذا اخترت Present Modally سوف يظهر من الأسفل الى الأعلى والعكس عندها يعود للصفحة السابقة ! كما الحال في تطبيقنا
     
    لكن بدون استخدام الـ Navigation Bar الـ Show يظهر كالـ Present Modally
     
    الطريقة الثالثة :
     
    الطريقة هذه تدعى Unwind Segue
     
    ما الذي يميزها ؟
    الذي يميزها هو التالي :
     
    أولا : يمكنك العودة الى أي صفحة تريدها وليس ملزماً بالعودة الى الصفحة السابقة فقط !
    بما يعني كما هو حال مثالنا سوف يمكنك العودة من صفحة 3 الى صفحة 0 بشكل مباشر
     
    ثانيا:
    يمكنك ارجاع بيانات من الصفحة الحالي الى الصفحة السابقة !
    بما يعني سوف تستطيع ارجاع بيانات موجوده في صفحة 3 الى صفحة 0 او أي صفحة تريدها !!
    معلومة :

    الـ Unwind Segue تعني فك الـ Segue وبالتالي يجب أن يكون هناك Segue لتستخدم هذه الطريقة !
    اذا استخدمتها بدون عمل Segue مسبقاً بين 2 من الـ View Controller او اكثر ، سوف يسبب Crash للتطبيق
     
    الان نبدأ في شرح الطريقة:
     
    كما شرحت الـ Segue سوف اقسم الـ Unwind Segue الى عدة طرق
     
    طريقة الأولى :
    ترغب فقط بالعوده الى صفحة معينه بدون ارجاع أي بيانات
    وبالتالي تحتاج تفهم هذه النقطة :
    اذا رغبت بالرجوع الى صفحة معينه سوف تحتاج الى كتابة كود في صفحة التي تريد الرجوع لها هنا نحن نريد العوده من أي صفحة الى الصفحة الرئيسية View Controller 0
    فسوف نقوم بكتابة الكود التالي :
    @IBAction func unwindToHome(segue:UIStoryboardSegue) {     }  
    في ملف اكواد  View Controller 0
     
    ملاحظة :
    -لا تحتاج الى كتابة أي اكواد في الداخل الاقواس ! على الأقل في الوقت الحالي !
    - تستطيع تسمية الـFunction بأي اسم تريده ، انا قمت بتسميته unwindToHome
     
    ومن ثم ننتقل الى الـ Storyboard ونفعل التالي مع صفحات 1 و  2و 3
    شاهد الصورة :

     
    فقط هذا كل ما نحتاج الى فعله !
    قم بتشغيل التطبيق وسوف تجده يعمل بمجرد الضغط على زر Return to Home سوف تجده يعود الى الصفحة الرئيسية وهيا View Controller 0
     
    الطريقة الثانية :
    ماذا اذا اردت تنفيذ امراً معيناً قبل ان يحدث الـ Unwind Segue ؟
    تحتاج الطريقة دي في بعض الحالات ، مثلا صفحة Login المستخدم بعد ما يكتب اسم المستخدم وكلمة السر ويضغط زر Login سوف تحتاج الى الاتصال بالسيرفر وتتأكد انه المستخدم موجود في قاعدة بياناته وبعد التأكد ، تغلق الصفحة باستخدام  Unwind Segue وتحوله الى الصفحة الرئيسية
    معلومة :
    ما سبق ذكره مجرد مثال ، لأنه تستطيع أيضا استخدام سطر
    dismiss(animated: true, completion: nil)  
    لأغلاق الصفحة فالمثال السابق يعتبر كطريقة أخرى لتنفيذ نفس الأمر, على أي حال الطريقة مشابه لطريقة الـ Segue من حيث تحتاج الى إعطاء مُعرف.
    نعود للشرح :
     
    اهم نقطه هنا هو فصل الربط الذي عملته في الطريقة الاولى بين الـ Button و Exit ولكن لا تحذف الـ  Function الذي كتبناه في View Controller 0
     
    @IBAction func unwindToHome(segue:UIStoryboardSegue) {}  
    في هذه الطريقة سوف نربط الـ Viewcontroller نفسه مع الـ Exit
     
    شاهد الصورة التالية :
     

     
     
    ومن ثم سوف نعطي للـ Unwind Segue مُعرف
    شاهد الصورة التالية :
     

     
    قمت باعطاء مُعرف home الان قم بربط الـ button مع ملف الاكواد ومن ثم استخدم نفس السطر الذي استخدمناه في الـ Segue
    performSegue(withIdentifier: "home", sender: nil)  
    بالطريقة هذه تستطيع عمل أي امر تريده قبل حدوث الـ Unwind Segue
     
    الطريقة الثالثة :
     
    في هذه الطريقة الامر عائد اليك ، يمكنك عمل الطريقة الاولى وتنفذ الطريقة هذه معها او استخدام الطريقة الثانية مع هذه الطريقة.
    فالطريقة الثالثة تشرح طريقة نقل البيانات من الصفحة 3 الى صفحة 1
     
    بنفس درجة حاجتك الى استخدام الى الـ Segue لنقل البيانات أيضا سوف تحتاج استخدام Unwind Segue لنقل البيانات
     
    من امثله استخدام الـ Unwind Segue لنقل البيانات, تطبيقات المحادثه مثل الـ Whatsapp عند استخدامك للتطبيق لأول مره يطلب منك اختيار رمز دولتك فعند الضغط عليه يوجهك لصفحة تختار فيها دولتك ومن ثم عند اختيار دولتك يعود الى صفحة كتابة رقم جوالك هنا حدث Unwind Segue بحيث تم نقل رمز الدولة من صفحة أخرى الى صفحة السابقة
     
    نعود للشرح :
     
    هل تذكر ماذا فعلنا عند نقل معلومات او بيانات من صفحة 1 الى الصفحة 2 باستخدام الـ Segue ؟ الطريقة مشابهه لحد ما ! نحن نريد نقل بيانات من صفحة 3 الى صفحة 0 لذا قبل أنا نبدأ سوف نفعل التالي:
    سوف نضيف Textfield في الصفحة 3 ونضيف Label في صفحة 0 الي هيا الصفحة الرئيسية الذي نريد الرجوع اليها ونقوم بربطهم بملف الاكواد
    شاهد الصورة التالية :

     
    الان حان الوقت للكتابة في Function
    @IBAction func unwindToHome(segue:UIStoryboardSegue) { }  
    الان كما قلنا سابقا نريد نقل النص من الـ Textfield الموجود في View Controller 3 الى الـ Label الموجود في View Controller 0 لذا نقوم بكتابة التالي :
     
     @IBAction func unwindToHome(segue:UIStoryboardSegue) { if segue.identifier == "home" {       let vc = segue.source as! ViewController3 label1.text = vc.text1.text       }      }  
    اول شيء نقوم بالتأكد من اسم المُعرف في حال كنت تتبع الطريقة الاولى لن يكون هناك اسم معرف الا اذا وضعته باختيارك لذا يمكنك حذف هذا السطر
    if segue.identifier == "home" {}  
    وحتى أيضا اذا اتبعت الطريقة الثانية لن تحتاج هذا السطر! الا اذا كنت تريد ارجاع بيانات من صفحتين فأكثر فهنا يتوجب عليك التمييز بينهم عن طريق التأكد من اسم المُعرف على أي حال اذا لاحظت فالكود مشابه جدا من طريقة الـ Segue .
    في الـ Segue نقوم بكتابته بداخل Function يسمى prepare وهنا بداخل Function الـ Unwind وأيضا في الـ Segue نكتب
    segue.destination as اسم الكلاس الذي نريد الانتقال اليه وفي الـ Unwind Segue نكتب
    segue.source as اسم الكلاس الذي سوف نعود منه لذا تقريبا نفس الفكرة الاختلاف في كلمة destination و source
    اخيراً قم بتشغيل التطبيق
     
    وشاهد النتيجة في الصورة التالية :

     
    وبكذا انتهينا من هذا الموضوع
     
    في هذا الموضوع فهمت الطريقة الصحيحة لنقل البيانات بين الـ View Controllers وماهي الاستخدامات الخاطئة والفريق بين Segue و Unwind Segue.
    قد تتسأل هل هذه هيا الطرق الوحيدة لنقل البيانات ؟
    الاجابه هيا لا ، ما تم شرحه في الموضوع هو طرقتين لنقل البيانات Segue و Unwind Segue وهما اشهر واكثر طرقتين لنقل البيانات بين الـ View Controllers استخداماً
    لكن هناك طرق أخرى أيضا !
     
    مستوى المقال: متوسط

    بواسطه باسل العمودي , في

  • بسم الله الرحمن الرحيم
     
    سنتعرف في هذا الدرس على طريقة انشاء تطبيق يدعم تعدد اللغات بحسب لغة الجهاز، سنعتمد اللغتين العربية والانجليزية في هذا التطبيق.
     
    سنبدأ أولا باضافة جميع اللغات التي نريد دعمها في تطبيقنا
    نذهب الى Project Navigator

    ثم نقوم باضافة اللغات المطلوبة من الأسفل

    نلاحظ تقسيم الملفات الى لغتين كما في الصورة

    االآن سنقوم بانشاء ملف من نوع String لنخزن فيه جميع الStrings باللغتين العربية والانجليزية
    اضغط على File > New > File واختر النوع String كما في الصورة

    تأكد من تسمية الملف باسم "Localizable" تماما كما هو ليتعرف عليه الxCode بشكل صحيح
    الآن نختار الملف الجديد من اليسار، ومن قائمة الFile Inspector نضغط على Localize ونختار اللغة الانجليزية مبدئيا

    الآن نختار اللغة العربية أيضا لنقوم بتقسيم ملف الLocalizable.strings الى لغتين

    قم باضافة الStrings المطلوبة الى الملفين بالطريقة التالية

    ليقوم الxCode بعمل Build تأكد من:
    كتابة الkey بشكل موحد بين اللغتين العربية والانجليزية وضع علامة ال= بين الkey والvalue كتابة الvalue بين علامتي تنصيص "" انهاء السطر بSemi-Colon ;  
     
    لنرى طريقة استدعاء الStrings  من ملف Localizable قمت بعمل الواجهة البسيطة التالية:

    وقمت بربط الOutlets والActions كالتالي:

     
    الآن اضف الكود التالي في زر اللغة الانجليزية:
    let path = Bundle.main.path(forResource: "en", ofType: "lproj") let bundle = Bundle.init(path: path!)! as Bundle nameLbl.text = bundle.localizedString(forKey: "WebsiteName", value: nil, table: nil) والتالي لزر اللغة العربية:
    let path = Bundle.main.path(forResource: "ar", ofType: "lproj") let bundle = Bundle.init(path: path!)! as Bundle nameLbl.text = bundle.localizedString(forKey: "WebsiteName", value: nil, table: nil)  
    في السطر الأول: قمنا بتعريف الpath الذي سيوصلنا لكل من ملفات اللغة العربية والانجليزية
    (يمكنك معرفة الResource والType من قائمة الFile Inspector عند الضغط على ملفي العربية والانجليزية)

    في السطر الثاني قمنا بتعريف كل من ملفات اللغتين كVariable يحمل الاسم bundle
    في السطر الثالث قمنا باستدعاء الString المطلوب باستخدام الميثود localizedString(_:String,_:String,_:Strint( 
     
    والآن عند تجربة البرنامج نرى النتيجة التالية (صورة GIF):
     

     
    حتى الآن كنا نحدد ملف اللغة الذي نريد استدعاء الString منه
    والآن سنقوم بتغيير النص اعتمادا على لغة الجهاز باستخدام زر واحد فقط
    لنرى ذلك قمت بتغيير الStoryboard ليحتوي على زر واحد فقط، وقمت باضافة الoutlets والactions كالتالي:


     
    والآن قم باضافة السطرين التاليين للميثود viewDidLoad()
    let btnTitle: String = NSLocalizedString("ButtonText", comment: "") languageBtn.setTitle(btnTitle, for: .normal) والسطر التالي للميثود الخاصة بالbutton action
    nameLbl.text = NSLocalizedString("WebsiteName", comment: "") في هذه الاكواد، قمنا باستعمال الميثود NSLocalizedString لاستدعاء الString المناسب بحسب لغة الجهاز
    لتجربة البرنامج سنقوم بتشغيله على جهاز بلغة انجليزية، ثم سنحولها للعربية ونعيد التجربة
    (صورة GIF):


     
     
    وصلنا الى ختام الدرس، أستودعكم الله الذي لا تضيع ودائعه.
    مستوى المقال: متوسط

    بواسطه AMR0T , في

  • ماهو ال Model View Controller (MVC)؟
     
    MVC هو مبدأ او نموذج معماري architectural pattern يستخدم للتعامل مع واجهات المستخدم في تطبيقات iOS.
    هذا المبدا مهم ان تحاول فهمه لانه اساس برمجة تطبيقات ال iOS . فعندما تبدأ برمجه مشروعك عليك تقسيمه الى ثلاثة اقسام كالتالي
    Model : عباره عن مجموعة البيانات أو data في تطبيقك . مثلا لو لدينا تطبيق لعرض موديلات السيارات.  كل المعلومات عن السياره مثل الماركه, اللون وغيرها تعتبر بيانات ويتم تخزينها في كلاس.
    View : عباره عن الواجهه الظاهره لمستخدم تطبيقك.
    في xcode تعتبر  ال view هي العناصر المستخدمه في  storyboard  واللتي نقوم بربطها بالكود مثل UILabel, UIView and UIImage.
    Controller : هو الرابط  او حلقة الوصل بين ال model & view اي بين البيانات والواجهات . فهو يقوم بتزويد ال  view  بالبيانات اللتي تحتاجها من model. ويقوم بتحديث ال model حين يدخل  المستخدم بيانات جديده الى ال view
    هذا الجزء يعتبر الدوال او method او ال action المستخدمه في برمجه العناصر كالازرار مثلا.
     
    الان نستعرض مثال بسيط لشرح الفكره وتعميق فهمها.( من هنا تستطيع البدء والبحث عن المزيد عن هذا المفهوم وتطبيقه).
    لنفرض ان لدينا مشروع  يستعرض ماركة و لون السياره . الان وفقا لهذا المفهوم سنقسم كالتالي
      Model: ننشئ class  نسميه car ونضع فيه بيانات السياره (ماركه brand, لون  color).

    View: هي واجهة المستخدم سننشئها كالتالي:

     
    وننشئ outlet لكل من ال labels كالتالي

     
    Controller: وهي الاوامر المستخدمه لربط عناصر الواجهه بالبيانات.
    - انشأنا object  اسمه car1 من كلاس car حتى نتمكن من الوصول الى خصائص الكلاس (الماركه و اللون ).
    - باستخدام ال object المسمى car1 وصلنا الى خاصية brand ووضعنا فيها قيمه lexus . وخاصية color وضعنا فيها قيمه Red
    - الان مرحله الربط بين  عناصر الواجهه والبيانات فالامر Brand.text يشير الى ان نضع في ال label الموجود في الواجهه  النص الموجود في خاصية Car1.Brand وهو في هذه الحاله lexus. نفس الامر لعنصر اللون.  
     

     
    الشكل النهائي للكود في الصوره التاليه لكن هناك ملاحظه يفضل أن تنشئ ملف سويفت منفصل لتضع فيه كلاسات ال model .

     
    الان نقوم بتشغيل التطبيق لرؤيه النتيجه
     

     
    اتمنى أنني وفقت في شرح هذا المفهوم المهم بطريقه سهله وبسيطه ـ شكرا لكم ولعالم البرمجه
     
     
    مستوى المقال: متوسط

    بواسطه marwaalgethami , في

  • ماهو الcallback hell،  جحيم الكولباك؟
    لا يوجد شيء مميز في الجافا سكربت يسمى بجحيم الكولباك ما هو الا تسميه لأمر ما قد يقع فيه المطور وهو طريقة كتابة كود حيث يحتوي على عدة دوال Asynchronous مترابطة مع بعضها، واحدة داخل الاخرى،  مثال:
     
    getData1(function(x){ getData2(x, function(y){ getData3(y, function(z){ ... }); }); });  
    والسبب في ذلك يعود لأن ناتج "results" كل دالة يعتمد على ما سبقتها.
     
    ملاحظة: كلمة asynchronous تعني أمر يحدث لاحقا << لا تركز على هالشي مو موضوعنا.
     
    جحيم الكولباك أمر ليس بجيد للكود، خصوصا في الحالات المعقدة التي تكون فيها دوال كثيرة مربوطه مع بعضها البعض وهنا قد يؤخر وقت انتهاء من التنفيذ و كمطور انت لا تريد ان تقع في هذه المشكلة, لحل مشكلة جحيم الكولباك، يستخدم المطورون إحدى مكتبات البرومس promises "الوعد، الوعود"، فالوعد دالة إما أن يتم العمل به fulfilled مع اظهار نتيجة الوعد value او رده rejected مع إظهار السبب error، وقد يكون الكود يحتوي على أكثر من وعد، واحد تلو الآخر.
     

     
    شخصيا لا اعتمد على الوعود في حل مشكلة جحيم الكولباك، عند التعامل مع قواعد البيانات MySQL رغم أنها الطريقة المثلى ولكن تتطلب الكثير مع الكود "وجهة نظر" و قد تواجهك مشاكل في أداء performance التطبيق بعد ذلك, استخدم طريقة أكثر بدائية و فعالة "تفي بالغرض" ، مبدأ الفكرة و طريقة عملها تتشابه كثيرا مع مبدأ عمل promises ولكن بكود أقل، سأرفق الطريقة مع المثال بالأسفل.
     
    للتعرف أكثر على جحيم الكولباك، سأعرض لكم مثال على التعامل مع قاعدة البيانات MySQL بالـ NodeJS وعمل اكثر من query وناتج كل query يعتمد على كل سبقتها.
    var express = require("express"); var app = express(); var mysql = require('mysql'); var conn = mysql.createConnection({ host: 'localhost', user: 'me', password: 'secret', database: 'my_db' }); conn.connect(); // GET method route app.get('/user/posts', function (req, res) { /* http://www.example.com/user/posts?userId=26 */ var userId = req.query.userId; conn.query('SELECT username FROM users WHERE ID = ?', [userId], function (error, results, fields) { if (error) throw error; if (results.length) { var username = results[0].username; conn.query('SELECT * FROM posts WHERE post_by = ?', [username], function (error, posts, fields) { if (error) throw error; if (posts.length) { conn.query('SELECT * FROM users_addresses WHERE username = ?', [username], function (error, address, fields) { if (error) throw error; if (statistics.length) { res.json(200, { posts: posts, address: address }); } else { res.json(200, { error: 'no statistics found' }); } }); } else { res.json(200, { error: 'no posts found' }); } }); } else { res.json(200, { error: 'no user found by ID 26' }); } }); });  
    للتخلص على مثل هذه المشاكل في حالة التعامل مع MySQL / MariaDB يكون بإسناد results كل عملية query في دالة مثلا
    var express = require("express"); var app = express(); var mysql = require('mysql'); var conn = mysql.createConnection({ host: 'localhost', user: 'me', password: 'secret', database: 'my_db' }); conn.connect(); // GET method route app.get('/user/posts', function (req, res) { /* http://www.example.com/user/posts?userId=26 */ var userId = req.query.userId; conn.query('SELECT username FROM users WHERE ID = ?', [userId], function (error, results, fields) { if (error) throw error; if (results.length) { setUsername(results[0].username); } else { res.json(200, { error: 'no user found by ID 26' }); } }); function setUsername(user) { var username = user; conn.query('SELECT * FROM posts WHERE post_by = ?', [username], function (error, posts, fields) { if (error) throw error; if (posts.length) { setPosts(posts, username); } else { res.json(200, { error: 'no posts found' }); } }); } function setPosts(po, user) { var posts = po; // po is the result of the prev query and it is an object it could be length of 1 or more doesn't matter var username = user; conn.query('SELECT * FROM users_addresses WHERE username = ?', [username], function (error, address, fields) { if (error) throw error; if (statistics.length) { res.json(200, { username: username, posts: posts, address: address }); } else { res.json(200, { error: 'no statistics found' }); } }); } });  
    أو بطريقة اخرى وهي عمل execution لكل query بطريقة parallel او series  بإستخدام مكتبة async
    /* Make Sure To Install async npm install async --save */ var async = require(“async”); // Parallel Method async.parallel([ wait5SecondsAndReturn1, wait5SecondsAndReturn2 ], function (err, results){ //after 5 seconds, results will be [1, 2] }); function wait5SecondsAndReturn1(callback) { setTimeout(function(){ callback(null, 1); }, 5000); } function wait5SecondsAndReturn2(callback) { setTimeout(function(){ callback(null, 2); }, 5000); } //Series Method async.series([ wait5SecondsAndReturn1, wait5SecondsAndReturn2 ], function (err, results){ //after 10 seconds, results will be [1, 2] }); function wait5SecondsAndReturn1(callback) { setTimeout(function(){ callback(null, 1); }, 5000); } function wait5SecondsAndReturn2(callback) { setTimeout(function(){ callback(null, 2); }, 5000); } // Another one async.series([ function(callback) { setTimeout(function() { console.log(“Task 1”); callback(null, 1); }, 300); }, function(callback) { setTimeout(function() { console.log(“Task 2”); callback(null, 2); }, 200); }, function(callback) { setTimeout(function() { console.log(“Task 3“); callback(null, 3); }, 100); } ], function(error, results) { console.log(results); });  
    وبذلك تستطيع التخلص من جحيم الكولباك بدون promises التي يلقى الكثير من المبرمجين المبتدأئين صعوبة في التعامل مع هذا النوع من الدوال.
     
    ملاحظات حول المثال السابق:
    الأخذ بالاعتبار جميع الشروط من ان هذا المستخدم user لدية احصائية مسجله و بوستات, ان لم يكن تستطيع التخلص من الشرط if(posts.length) لا يهم , بالتالي سيتم تمرير اوبجكت بدون قيم. بعض المبرمجين قد لا يتفق مع العملية, احترم ذلك ولكنها تفي بالغرض مع ES2015 و مايليه .. اسناد اوبجكت الى متغير يعطيه نفس قيمه الاوبجكت بشكل مباشر.
    مستوى المقال: مبتدئ

    بواسطه AbdullaScript , في

  • Ads Belongs To This website

    عالم البرمجة

    عالم البرمجة مقالات برمجة و دورات مجانية لإحتراف البرمجة هدفنا تبسيط البرمجة ونشرها بيد الكل بشكل ممتع ومتطور ومحدث بإستمرار لمواكبة جديد تطورات البرمجة الحديثة و المتقدمة بدون مقابل