جميع الأنشطة

يتم تحديث محتوى هذا السجل تلقائيا   

  1. الاسبوع الماضي
  2. السلام عليكم و رحمة الله و بركاته. ما هو الـException ؟ الـException هو حدث معين يعمل على تغيير المجرى الطبيعي للبرنامج اذا حدث خطأ بحيث يتم التعامل معه بطريقة معينة. ماذا يحدث عند ظهور Exception ؟ يتم حفظ الحالة الحالية للكود. يتم تحويل مجرى البرنامج الى function معينة تتعامل مع هذا الـException. و أخير، على حسب تصميم البرنامج يتم استكمال البرنامج من نقطة الحفظ أو ايقاف عمل البرنامج أو تحويل مجرى البرنامج الى مكان آخر في الكود. ملاحظة: يجب استخدام الـException فقط عند ظهور الخطأ ولا يجب استخدامه فقط للانتقال الى مكان آخر في الكود. و في هذا الدرس سوف: نشرح الاستخدام الأساسي للـExceptions. كيفية بناء Exception خاص(Custom Exception). الأستخدام الأساسي: يتم استخدام (throw) لالقاء Exception بحيث يتم انشاء اوبجت من الكلاس Exception و تمرير اليه الرسالة المراد اظهارها كما موضح الكود بالأسفل. <?php //انشاء fucntion و القاء بداخلها Exception function checkNum($number) { if($number>1) { throw new Exception("Value must be 1 or below"); } return true; } //هذا الاستدعاء سوف يظهر الـExcpetion checkNum(2); ?> و اذا أجرينا excute لهذا الكود سوف يظهر لنا التالي: Fatal error: Uncaught exception 'Exception' with message 'Value must be 1 or below' in C:\webfolder\test.php:6 Stack trace: #0 C:\webfolder\test.php(12): checkNum(28) #1 {main} thrown in C:\webfolder\test.php on line 6 كما نلاحظ عند استدعاء الـFunction تم اظهار الـException و اظهار الرسالة معه، و لكن يجب علينا التعامل مع هذا الـException. Try and Catch: حتى نتعامل مع الـException الظاهر في المثال السابق يجب علينا استخدام try and catch كما موضح أدناه. <?php //انشاء function و القاء Exception function checkNum($number) { if($number>1) { throw new Exception("Value must be 1 or below"); } return true; } //الـException سوف يظهر في الـtry try { checkNum(2); //اذا تم اظهار الـException الكود بالأسفل لن يتم عمله. echo 'If you see this, the number is 1 or below'; } //الامساك بالـException و عمل شيء معين به. catch(Exception $e) { echo 'Message: ' .$e->getMessage(); } ?> و اذا أجرينا excute لهذا الكود سوف يظهر لنا التالي: Message: Value must be 1 or below شرح المثال السابق: تم انشاء checkNum() و التي تعمل على التحقق من أن الرقم أكبر من 1، فاذا كان كذلك يتم القاء الـException. الـfunction و التي تسمى checkNum() يتم استدعاؤها في داخل الـtry block حتى يتم التعامل مع احتمالية ظهور الException من الـfunction المستدعاة الـcatch block تستقبل الـexception و تتعامل معه بحيث يتم طبع الرسالة عن طريق استخدام الـgetMessage(). كيفية انشاء Exception خاص: حتى يتم انشاء Exception خاص ( Custom Exception handler) يجب علينا انشاء كلاس و يقوم هذا الكلاس بـextends للكلاس Exception كما هو موضح أدناه. <?php class InvalidCCNumberException extends Exception { public function __construct($message = 'No CC Number', $code = 0, $previous = null) { parent::__construct($message, $code, $previous); } } function processCC($num = null) { if (is_null($num)) { throw new InvalidCCNumberException(); } //سوف يتم استدعاء الاكود بالاسفل اذا لم يحصل الـException echo 'processed'; } try{ processCC(); } catch (InvalidCCNumberException $e) { echo "<pre>"; echo $e->getMessage(); echo "<br>"; echo get_class($e); } finally { echo "\nfinal!"; } ?> و لقد تم عمل التالي: انشاء كلاس و سميناه على سبيل المثال InvalidCCNumberException بحيث يقوم بـextends الكلاس Exception فيتم inherit جميع الميثودز و المتغيرات. قمنا بعمل override للميثود __construct() و التي تقوم بتمرير الرسالة المعطاة للأوبجكت من كلاس Exception. انشاء function وتسميتها processCC و التي بدورها تقوم بـthrow الـException اذا كان الـparameter فارغ (null). قمنا باستدعاء الـfunction داخل الـtry block حتى نتعامل مع الـException اذا ظهر. في الـcatch block طبعنا الرسالة و الكلاس الخاص بأوجكت الـException. و بالنهاية أضفنا الـfinally block و التي سوف يتم تنفيذ الكود بداخلها سواء ظهر الـException او لم يظهر. و اذا أجرينا excute لهذا الكود سوف يظهر لنا التالي: No CC Number InvalidCCNumberException final! في النهاية، هناك بعض كلاسات الـException المعرفة سابقا و المتواجدة في مكتبات اللغة الخاصة بالـPHP و منها الموضح أدناه في الصورة. بحيث يتم استخدامها لتسهيل عملية الـdebugging. و هنا وصلنا الى نهاية هذا الدرس.😄 أتمنى أني قد وفقت بايصال المعلومة.
  3. Earlier
  4. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته في هذا الموضوع، سنستعرض الStreams، وهي عبارة عن API جديدة ورائعه تم اضافتها في التحديث الأخير للJava 8. باستخدام الStreams، ستستطيع معالجة بيانات الCollections كأنك تقوم بعمل Query في احد الSQL databases، وذلك بطريقة تعريفية بسيطة دون الحاجة لكتابة Loop في كل مرة تريد معالجة البيانات لتخرج بنتيجة معينة (جمع عناصر الArrayList على سبيل المثال)، وذلك بكتابة سطر برمجي واحد فقط! هناك ميزة أخرى أيضا للStreams، وهي امكانية تفعيل جميع أنوية المعالج لتسريع عملية تنفيذ البرنامج دون الحاجة الى كتابة سطر واحد من كلاس الThreads المزعج! ما هو الStream؟ سنذكر تاليا أهم سمات الStream في الجافا: تسلسل عناصر: الStream هو عبارة عن تسلسل لعناصر يتم استخلاصها من أحد المصادر (Sources) -قد تكون Array أو ArrayList الخ-، حيث يقوم الStream بـ قراءة/معالجة بيانات هذه المصادر عند الحاجة فقط المصادر: قد تكون Collection, Arrays, I/O العمليات: يدعم الStream مجموعة من العمليات (أو methods) على سبيل المثال لا الحصر: filter, map, limit, reduce, find, match تكرارات تلقائية: الStreams تدعم التكرارات التلقائية (Automatic Iterations)، أي كما قلنا سابقا، لن تكتب المزيد من الloops، كل ماعليك فعله هو كتابة سطر واحد باستعمال الStreams والجافا ستقوم بالباقي إنشاء الStream في الجافا 8، هناك طريقتان لإنشاء Stream: ()stream: ستنشئ ستريم تسلسلي مربوط بcollection، ولكن بدون الاستفادة من ميزة تعدد أنوية المعالج في التنفيذ ()parallelStream: ستنشئ ستريم متوازي مربوط بcollection، مستفيدًا من ميزة تعدد أنوية المعالج في التنفيذ مثال: List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); أهم ميثودز الStream ()forEach: هذه الميثود تعطيك امكانية المرور بجميع عناصر الستريم للقيام بعملية معينة، في المثال التالي سنقوم بطباعة 10 ارقام عشوائية باستخدام forEach: Random random = new Random(); random.ints().limit(10).forEach(System.out::println); ()map: بهذه الميثود تستخدم لربط كل عنصر بالستريم مع نتيجة خاصة به. في المثال التالي سنقوم بطباعة مربعات ارقام مميزة باستخدام map: List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); //get list of unique squares List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList()); ()filter: يتم استخدام هذه الميثود للتخلص من بعض عناصر الستريم بناء على شروط معينة، المثال التالي يقوم بطباعة عدد ال Strings الفارغة: List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); //get count of empty string int count = strings.stream().filter(string -> string.isEmpty()).count(); ()limit: هذه الميثود تستخدم للحد من حجم الستريم بدون أي شروط، في المثال التالي سنقوم بطباعة 10 ارقام عشوائية باستخدام limit: Random random = new Random(); random.ints().limit(10).forEach(System.out::println); ()sorted: يتم استخدام هذه الميثود لترتيب الستريم، الكود التالي يظهر كيف تقوم بطباعة 10 ارقام عشوائية مرتبة: Random random = new Random(); random.ints().limit(10).sorted().forEach(System.out::println); ( لاحظ كيف أنجزنا المهمة بسطر واحد فقط، تخيل لو أردت أن تقوم بطباعة 10 ارقام عشوائية مرتبة بالطريقة التقليدية! ) Parallel Processing: هذه هي الطريقة الثانية لإنشاء الستريم الموازي (استعمال جميع أنوية المعالج لتنفيذ البرنامج) كما هو مذكور بالأعلى: List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); //get count of empty string int count = strings.parallelStream().filter(string -> string.isEmpty()).count(); لاحظ أنك تستطيع التبديل بين الStream والparallelStream بسهولة تامة. Collectors: نستخدم الCollectors لجمع نتائج معالجة بيانات الستريم واعادتها على شكل List. List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println("Filtered List: " + filtered); String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("Merged String: " + mergedString); في المثال التالي، سنقوم بعمل مقارنة بين الJava 7 والJava 8 في برنامج يقوم بضم الStrings غير الفارغة. import java.util.*; public class Java8StreamTest { public static void main(String[] args) { // Java 7 approach System.out.println("Using Java 7: "); List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); System.out.println("List: " +strings); StringBuilder stringBuilder = new StringBuilder(); for (String string: strings) { if (!string.isEmpty()) { stringBuilder.append(string); stringBuilder.append(", "); } } String mergedString = stringBuilder.toString(); System.out.println("Merged String: " + mergedString); // Java 8 approach System.out.println("Using Java 8: "); System.out.println("List: " +strings); mergedString = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("Merged String: " + mergedString); } } وهنا نصل الى ختام موضوعنا، أسأل الله لي ولكم التوفيق والسداد.
  5. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته في هذا الموضوع، سنستعرض الStreams، وهي عبارة عن API جديدة ورائعه تم اضافتها في التحديث الأخير للJava 8. باستخدام الStreams، ستستطيع معالجة بيانات الCollections كأنك تقوم بعمل Query في احد الSQL databases، وذلك بطريقة تعريفية بسيطة دون الحاجة لكتابة Loop في كل مرة تريد معالجة البيانات لتخرج بنتيجة معينة (جمع عناصر الArrayList على سبيل المثال)، وذلك بكتابة سطر برمجي واحد فقط! هناك ميزة أخرى أيضا للStreams، وهي امكانية تفعيل جميع أنوية المعالج لتسريع عملية تنفيذ البرنامج دون الحاجة الى كتابة سطر واحد من كلاس الThreads المزعج! ما هو الStream؟ سنذكر تاليا أهم سمات الStream في الجافا: تسلسل عناصر: الStream هو عبارة عن تسلسل لعناصر يتم استخلاصها من أحد المصادر (Sources) -قد تكون Array أو ArrayList الخ-، حيث يقوم الStream بـ قراءة/معالجة بيانات هذه المصادر عند الحاجة فقط المصادر: قد تكون Collection, Arrays, I/O العمليات: يدعم الStream مجموعة من العمليات (أو methods) على سبيل المثال لا الحصر: filter, map, limit, reduce, find, match تكرارات تلقائية: الStreams تدعم التكرارات التلقائية (Automatic Iterations)، أي كما قلنا سابقا، لن تكتب المزيد من الloops، كل ماعليك فعله هو كتابة سطر واحد باستعمال الStreams والجافا ستقوم بالباقي إنشاء الStream في الجافا 8، هناك طريقتان لإنشاء Stream: ()stream: ستنشئ ستريم تسلسلي مربوط بcollection، ولكن بدون الاستفادة من ميزة تعدد أنوية المعالج في التنفيذ ()parallelStream: ستنشئ ستريم متوازي مربوط بcollection، مستفيدًا من ميزة تعدد أنوية المعالج في التنفيذ مثال: List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); أهم ميثودز الStream ()forEach: هذه الميثود تعطيك امكانية المرور بجميع عناصر الستريم للقيام بعملية معينة، في المثال التالي سنقوم بطباعة 10 ارقام عشوائية باستخدام forEach: Random random = new Random(); random.ints().limit(10).forEach(System.out::println); ()map: بهذه الميثود تستخدم لربط كل عنصر بالستريم مع نتيجة خاصة به. في المثال التالي سنقوم بطباعة مربعات ارقام مميزة باستخدام map: List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); //get list of unique squares List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList()); ()filter: يتم استخدام هذه الميثود للتخلص من بعض عناصر الستريم بناء على شروط معينة، المثال التالي يقوم بطباعة عدد ال Strings الفارغة: List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); //get count of empty string int count = strings.stream().filter(string -> string.isEmpty()).count(); ()limit: هذه الميثود تستخدم للحد من حجم الستريم بدون أي شروط، في المثال التالي سنقوم بطباعة 10 ارقام عشوائية باستخدام limit: Random random = new Random(); random.ints().limit(10).forEach(System.out::println); ()sorted: يتم استخدام هذه الميثود لترتيب الستريم، الكود التالي يظهر كيف تقوم بطباعة 10 ارقام عشوائية مرتبة: Random random = new Random(); random.ints().limit(10).sorted().forEach(System.out::println); ( لاحظ كيف أنجزنا المهمة بسطر واحد فقط، تخيل لو أردت أن تقوم بطباعة 10 ارقام عشوائية مرتبة بالطريقة التقليدية! ) Parallel Processing: هذه هي الطريقة الثانية لإنشاء الستريم الموازي (استعمال جميع أنوية المعالج لتنفيذ البرنامج) كما هو مذكور بالأعلى: List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); //get count of empty string int count = strings.parallelStream().filter(string -> string.isEmpty()).count(); لاحظ أنك تستطيع التبديل بين الStream والparallelStream بسهولة تامة. Collectors: نستخدم الCollectors لجمع نتائج معالجة بيانات الستريم واعادتها على شكل List. List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println("Filtered List: " + filtered); String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("Merged String: " + mergedString); في المثال التالي، سنقوم بعمل مقارنة بين الJava 7 والJava 8 في برنامج يقوم بضم الStrings غير الفارغة. import java.util.*; public class Java8StreamTest { public static void main(String[] args) { // Java 7 approach System.out.println("Using Java 7: "); List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); System.out.println("List: " +strings); StringBuilder stringBuilder = new StringBuilder(); for (String string: strings) { if (!string.isEmpty()) { stringBuilder.append(string); stringBuilder.append(", "); } } String mergedString = stringBuilder.toString(); System.out.println("Merged String: " + mergedString); // Java 8 approach System.out.println("Using Java 8: "); System.out.println("List: " +strings); mergedString = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("Merged String: " + mergedString); } } وهنا نصل الى ختام موضوعنا، أسأل الله لي ولكم التوفيق والسداد. تم ترقية هذا الطرح المميز الى صفحة المقالات
  6. اللغة المستخدمة : swift3 البرنامج : xcode 8 السلام عليكم، في هذا الدرس سنتحدث عن واحدة من أهم المهارات التي تساعدك في بناء مشروعك في وقت قياسي و بنتائج ممتازة اعتمادا على استخدام مكتبات قام بإنشائها مطورون آخرون لتكون متاحة لجميع المطورين. لكن قبل ذلك علينا فهم بعض الأمور الأساسية للبدء ثم سنتحدث عن طريقة تنزيل المكتبات إلى مشاريعك. الTermianl : حتى تستطيع التعامل مع الملفات الخارجية يجب أن تتعلم في البداية طريقة التعامل مع الterminal Terminal : هي واجهة غير رسومية يمكن استخدامها لتنفيذ الأوامر العادية التي تقوم بها كإنشاء ملف أو حذفه.. الخ. للوصول إليها كل ما عليك فعله هو كتابة terminal في شريط البحث للوصول السريع إليها و ستظهر لك كأول نتيجة، قم بفتحها كما ذكرنا فإن الterminal هي واجهة غير رسومية يمكن فيها تنفيذ الأوامر عن طريق كتابتها بطريقة معينة و إهم الإوامر التي يجب التعرف عليها لهذا الدرس هي التالية : cd : و هي اختصار ل (change directory ) و هي تستخدم للانتقال إلى أحد المجلدات و هي تشبه فتحك لأي مجلد من المجلدات بالطريقة العادية. ls : و هي تستخدم لعرض كل محتويات المجلد الموجود بداخله حاليا. الآن قم بفتح الterminal و قم بكتابة : cd Desktop للانتقال إلى سطح المكتب الخاص بك الآن قم بكتابة ls لتظهر محتويات سطح المكتب. ستجد أن كل الملفات و المجلدات و التطبيقات الموجودة في سطح المكتب قد تم عرضها لك في الterminal. هذه أهم الأمور التي يجب معرفتها فيما يخص وحدة التحكم أو الterminal للمواصلة في هذا الدرس. الCocoapod : و الآن حتى تتمكن من تنزيل المكتبات و الحصول عليها لابد من تنزيل أداة على وحدة التحكم للحصول على إمكانية استخدام أوامر مهمة تمكنك من إضافة المكتبات مباشرة في مشاريعك في الxcode و هذه الأداة تسمى cocoa pod و حتى تقوم بتنزيلها على الterminal الخاص بك قم بزيارة موقعهم: https://cocoapods.org/ و انسخ السطر الموجود في خانة Install أو انسخه مباشرة من هنا : sudo gem install cocoapods و قم بلصقه في وحدة التحكم (terminal) لديك. قد يطلب منك إدخال رقمك سري، قم بإدخاله ثم اضغط زر return أو enter. حينها سيبدأ الterminal بتنزيل الcocoa pod. ملاحظة: قد لا تظهر لك أي إشارة في الterminal تشير إلى أن البرنامج يتم تنزيله حاليا، و لكن يمكنك التأكد إذا ما نظرت إلى الزر الأحمر في الزاوية اليسرى في الشريط العلوي و الخاص بإغلاق البرنامج. حيث يفترض أن تجد بداخله دائرة سوداء و التي تشير إلى أن البرنامج يقوم بتنفيذ أحد العمليات في الوقت الحالي كما في الصورة التالية... الآن بعد اكتمال التنزيل ستزول النقطة السوداء و ستظهر لك مجموعة من التعليمات تخبرك باكتمال التنزيل كما في الصورة التالية: الآن يمكنك أن تتعامل مع المكتبات الموجودة و تنزيلها إلى مشروعك. تنزيل المكتبات في الجزء التالي من الدرس سنقوم بتنزيل إحدى المكتبات على مشروع xcode و سنختار JTAppleCalendar و هي تستخدم لإنشاء تقويم و استخدامه في مشروعك، و لكن لن نتحدث عن طريقة إنشاء التقويم و التعامل معه و إنما الغرض شرح طريقة تنزيل المكتبات على مشروعك و استخدام الcocoa pod لذلك يمكنك اختيار أي خيار آخر لتنزيله. و الاختيارات يمكن الوصول إليها من عدة مصادر أهمها و أشهرها الgithup حيث يمكنك البحث هناك عن كل ما تريد. و هنا رابط المكتبة التي سنقوم بتنزيلها إلى المشروع : https://github.com/patchthecode/JTAppleCalendar إذا لنبدأ.. في البداية قم بإنشاء مشروع xcode جديد و سمه demo مثلا و قم بحفظه على سطح المكتب. -- صورة الآن قم بفتح الterminal.. نريد في البداية الوصول إلى موقع المجلد الذي يحوي كل ملفات المشروع لتنزيل المكتبة في ذلك المكان و لذلك يجب علينا استخدام أمر cd ثم إضافة المسار الخاص بالمشروع للوصول إليه من خلال الterminal. ولكن يمكن استخدام طريقة أسهل من عمل ذلك يدويا و هي : ١- قم بكتابة cd ثم مسافة ٢- قم بسحب المجلد من سطح المكتب و أفلته في الterminal و ستجد أن الterminal قام بإضافة مسار المجلد تلقائيا ٣- اضغط enter أو return الآن تم الوصول إلى داخل المجلد و يمكنك التأكد من ذلك عن طريق استعراض الملفات بداخل المجلد بالضغط على ls و ستجد أن الterminal يعرض لك ملفات المشروع الخاصة بك. الآن قم بكتابة الأمر التالي على الterminal : pod init ثم اضغط enter - هذا الأمر خاص بإنشاء ملف باسم pod file و هو الملف الذي يجب أن يحوي كل المكتبات التي تريد استخدامها في مشروعك. و للتأكد من أن الملف قد تم إنشاؤه قم بإدخال أمر ls مرة أخرى و يجب عليك أن تجد ملف جديد باسم Podfile أو يمكنك فتح المجلد يدويا و ستجد ذلك الملف بالداخل. - الآن قم بفتح ملف الpodfile حيث سنقوم بإضافة بعض الأسطر بداخله. - الآن عليك إضافة أسماء المكتبات التي ستقوم باستخدامها داخل هذا الملف. لكن من أين نحصل على اسم المكتبات ؟ عليك دائما أن تعود إلى التوجيهات المكتوبة من مصدر التنزيل ذاته للحصول على هذه المعلومات، لذلك قم بالعودة إلى الرابط مرة أخرى https://github.com/patchthecode/JTAppleCalendar دائما عند تنزيل المكتبات لابد من البحث على خانة الinstallation بطريقة الcocoa pod لذلك توجه إلى خانة installiation via cocoa pod و ستجد أنه يشير إلى أمر تنزيل الcocoa pod حيث قمنا بذلك مسبقا. و بعد ذلك يريك المحتويات التي يجب أن تكون بداخل ملف الPodFile لذلك انسخ منها الأشياء التي لا يحويها الملف الموجود عندك و هي التالية : source 'https://github.com/CocoaPods/Specs.git' platform :ios, '10.0' use_frameworks! و هذه التعليمات في الأعلى ضعها في بداية الملف pod 'JTAppleCalendar', '~> 7.0' و هذه ضعها بدلا من : #Pods for demo أخيرا ملف الpodFile يجب أن يبدو كالتالي : # Uncomment the next line to define a global platform for your project source 'https://github.com/CocoaPods/Specs.git' platform :ios, '10.0' use_frameworks! target 'demo' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! pod 'JTAppleCalendar', '~> 7.0' target 'demoTests' do inherit! :search_paths # Pods for testing end target 'demoUITests' do inherit! :search_paths # Pods for testing end end مع ملاحظة النقاط التالية : ١- أي سطر يبدأ ب# فهو كالcomment في البرمجة لا تأثير له و لذلك لا مشكلة في حال وجود سطور إضافية من هذا النوع في ملفي أو ملفك. ٢- لا مشكلة إذا لم يكن ملفك يحوي السطور التالية : target 'demoTests' do inherit! :search_paths # Pods for testing end target 'demoUITests' do inherit! :search_paths # Pods for testing end ٣- قد يكون هناك اختلاف في الاسم إذا لم تقم بتسمية مشروعك باسم demo و لا مشكلة في ذلك. - الآن أصبح ملف الpodfile جاهزا كل ما عليك فعله هو حفظ التغييرات التي قمت بإجرائها عليه ثم إغلاقه. - الآن تبقت خطوة أخيرة.. قم بالعودة للterminal و تأكد أنك ما زلت في مسار المشروع و قم بإدخال : pod install و اضغط enter سيبدأ الآن الterminal بتحميل المكتبات التي طلبتها و عندما يكتمل التحميل سيشعرك بذلك كما في الصورة.. الآن اذهب إلى ملف الxcode الذي كنت تعمل به و قم بإغلاقه ثم توجه إلى مجلد المشروع الخاص بك و ستجد أن هناك ملف xcode جديد ظهر لديك في المجلد.. حيث أن هذا هو نفس مشروعك الذي كنت تعمل به و لكن الفرق الوحيد أنه يضم كل المكتبات التي طلبت تنزيلها. و الآن لتتأكد أن التنزيل تم بطريقة صحيحة.. قم بفتح الملف الجديد و اذهب إلى أي كلاس و في أعلا الملف قم بكتابة أمر : import JTAppleCalender إذا لم يظهر البرنامج أي مشكلة فهذا يعني أنك قمت بتضمين المكتبة في مشروعك بنجاح. ملاحظة : قد يظهر الxcode خطأ في البداية عند كتابتك لهذا الأمر.. حينها قم بمسحه و قم بعمل build عن طريق cmd+b ثم اكتب الأمر مرة أخرى و يجب أن يعمل هذه المرة إلى هنا نصل إلى نهاية الدرس و إن كنت تود تجربة عمل الcalender و استخدام المكتبة فقم بمتابعة الفيديوهات الموجودة في رابط تنزيل المكتبة و قراءة التوجيهات الموجودة هناك. إلى اللقاء في دروس قريبة قادمة بإذن الله
  7. اللغة المستخدمة : swift3 البرنامج : xcode 8 السلام عليكم، في هذا الدرس سنتحدث عن واحدة من أهم المهارات التي تساعدك في بناء مشروعك في وقت قياسي و بنتائج ممتازة اعتمادا على استخدام مكتبات قام بإنشائها مطورون آخرون لتكون متاحة لجميع المطورين. لكن قبل ذلك علينا فهم بعض الأمور الأساسية للبدء ثم سنتحدث عن طريقة تنزيل المكتبات إلى مشاريعك. الTermianl : حتى تستطيع التعامل مع الملفات الخارجية يجب أن تتعلم في البداية طريقة التعامل مع الterminal Terminal : هي واجهة غير رسومية يمكن استخدامها لتنفيذ الأوامر العادية التي تقوم بها كإنشاء ملف أو حذفه.. الخ. للوصول إليها كل ما عليك فعله هو كتابة terminal في شريط البحث للوصول السريع إليها و ستظهر لك كأول نتيجة، قم بفتحها كما ذكرنا فإن الterminal هي واجهة غير رسومية يمكن فيها تنفيذ الأوامر عن طريق كتابتها بطريقة معينة و إهم الإوامر التي يجب التعرف عليها لهذا الدرس هي التالية : cd : و هي اختصار ل (change directory ) و هي تستخدم للانتقال إلى أحد المجلدات و هي تشبه فتحك لأي مجلد من المجلدات بالطريقة العادية. ls : و هي تستخدم لعرض كل محتويات المجلد الموجود بداخله حاليا. الآن قم بفتح الterminal و قم بكتابة : cd Desktop للانتقال إلى سطح المكتب الخاص بك الآن قم بكتابة ls لتظهر محتويات سطح المكتب. ستجد أن كل الملفات و المجلدات و التطبيقات الموجودة في سطح المكتب قد تم عرضها لك في الterminal. هذه أهم الأمور التي يجب معرفتها فيما يخص وحدة التحكم أو الterminal للمواصلة في هذا الدرس. الCocoapod : و الآن حتى تتمكن من تنزيل المكتبات و الحصول عليها لابد من تنزيل أداة على وحدة التحكم للحصول على إمكانية استخدام أوامر مهمة تمكنك من إضافة المكتبات مباشرة في مشاريعك في الxcode و هذه الأداة تسمى cocoa pod و حتى تقوم بتنزيلها على الterminal الخاص بك قم بزيارة موقعهم: https://cocoapods.org/ و انسخ السطر الموجود في خانة Install أو انسخه مباشرة من هنا : sudo gem install cocoapods و قم بلصقه في وحدة التحكم (terminal) لديك. قد يطلب منك إدخال رقمك سري، قم بإدخاله ثم اضغط زر return أو enter. حينها سيبدأ الterminal بتنزيل الcocoa pod. ملاحظة: قد لا تظهر لك أي إشارة في الterminal تشير إلى أن البرنامج يتم تنزيله حاليا، و لكن يمكنك التأكد إذا ما نظرت إلى الزر الأحمر في الزاوية اليسرى في الشريط العلوي و الخاص بإغلاق البرنامج. حيث يفترض أن تجد بداخله دائرة سوداء و التي تشير إلى أن البرنامج يقوم بتنفيذ أحد العمليات في الوقت الحالي كما في الصورة التالية... الآن بعد اكتمال التنزيل ستزول النقطة السوداء و ستظهر لك مجموعة من التعليمات تخبرك باكتمال التنزيل كما في الصورة التالية: الآن يمكنك أن تتعامل مع المكتبات الموجودة و تنزيلها إلى مشروعك. تنزيل المكتبات في الجزء التالي من الدرس سنقوم بتنزيل إحدى المكتبات على مشروع xcode و سنختار JTAppleCalendar و هي تستخدم لإنشاء تقويم و استخدامه في مشروعك، و لكن لن نتحدث عن طريقة إنشاء التقويم و التعامل معه و إنما الغرض شرح طريقة تنزيل المكتبات على مشروعك و استخدام الcocoa pod لذلك يمكنك اختيار أي خيار آخر لتنزيله. و الاختيارات يمكن الوصول إليها من عدة مصادر أهمها و أشهرها الgithup حيث يمكنك البحث هناك عن كل ما تريد. و هنا رابط المكتبة التي سنقوم بتنزيلها إلى المشروع : https://github.com/patchthecode/JTAppleCalendar إذا لنبدأ.. في البداية قم بإنشاء مشروع xcode جديد و سمه demo مثلا و قم بحفظه على سطح المكتب. -- صورة الآن قم بفتح الterminal.. نريد في البداية الوصول إلى موقع المجلد الذي يحوي كل ملفات المشروع لتنزيل المكتبة في ذلك المكان و لذلك يجب علينا استخدام أمر cd ثم إضافة المسار الخاص بالمشروع للوصول إليه من خلال الterminal. ولكن يمكن استخدام طريقة أسهل من عمل ذلك يدويا و هي : ١- قم بكتابة cd ثم مسافة ٢- قم بسحب المجلد من سطح المكتب و أفلته في الterminal و ستجد أن الterminal قام بإضافة مسار المجلد تلقائيا ٣- اضغط enter أو return الآن تم الوصول إلى داخل المجلد و يمكنك التأكد من ذلك عن طريق استعراض الملفات بداخل المجلد بالضغط على ls و ستجد أن الterminal يعرض لك ملفات المشروع الخاصة بك. الآن قم بكتابة الأمر التالي على الterminal : pod init ثم اضغط enter - هذا الأمر خاص بإنشاء ملف باسم pod file و هو الملف الذي يجب أن يحوي كل المكتبات التي تريد استخدامها في مشروعك. و للتأكد من أن الملف قد تم إنشاؤه قم بإدخال أمر ls مرة أخرى و يجب عليك أن تجد ملف جديد باسم Podfile أو يمكنك فتح المجلد يدويا و ستجد ذلك الملف بالداخل. - الآن قم بفتح ملف الpodfile حيث سنقوم بإضافة بعض الأسطر بداخله. - الآن عليك إضافة أسماء المكتبات التي ستقوم باستخدامها داخل هذا الملف. لكن من أين نحصل على اسم المكتبات ؟ عليك دائما أن تعود إلى التوجيهات المكتوبة من مصدر التنزيل ذاته للحصول على هذه المعلومات، لذلك قم بالعودة إلى الرابط مرة أخرى https://github.com/patchthecode/JTAppleCalendar دائما عند تنزيل المكتبات لابد من البحث على خانة الinstallation بطريقة الcocoa pod لذلك توجه إلى خانة installiation via cocoa pod و ستجد أنه يشير إلى أمر تنزيل الcocoa pod حيث قمنا بذلك مسبقا. و بعد ذلك يريك المحتويات التي يجب أن تكون بداخل ملف الPodFile لذلك انسخ منها الأشياء التي لا يحويها الملف الموجود عندك و هي التالية : source 'https://github.com/CocoaPods/Specs.git' platform :ios, '10.0' use_frameworks! و هذه التعليمات في الأعلى ضعها في بداية الملف pod 'JTAppleCalendar', '~> 7.0' و هذه ضعها بدلا من : #Pods for demo أخيرا ملف الpodFile يجب أن يبدو كالتالي : # Uncomment the next line to define a global platform for your project source 'https://github.com/CocoaPods/Specs.git' platform :ios, '10.0' use_frameworks! target 'demo' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! pod 'JTAppleCalendar', '~> 7.0' target 'demoTests' do inherit! :search_paths # Pods for testing end target 'demoUITests' do inherit! :search_paths # Pods for testing end end مع ملاحظة النقاط التالية : ١- أي سطر يبدأ ب# فهو كالcomment في البرمجة لا تأثير له و لذلك لا مشكلة في حال وجود سطور إضافية من هذا النوع في ملفي أو ملفك. ٢- لا مشكلة إذا لم يكن ملفك يحوي السطور التالية : target 'demoTests' do inherit! :search_paths # Pods for testing end target 'demoUITests' do inherit! :search_paths # Pods for testing end ٣- قد يكون هناك اختلاف في الاسم إذا لم تقم بتسمية مشروعك باسم demo و لا مشكلة في ذلك. - الآن أصبح ملف الpodfile جاهزا كل ما عليك فعله هو حفظ التغييرات التي قمت بإجرائها عليه ثم إغلاقه. - الآن تبقت خطوة أخيرة.. قم بالعودة للterminal و تأكد أنك ما زلت في مسار المشروع و قم بإدخال : pod install و اضغط enter سيبدأ الآن الterminal بتحميل المكتبات التي طلبتها و عندما يكتمل التحميل سيشعرك بذلك كما في الصورة.. الآن اذهب إلى ملف الxcode الذي كنت تعمل به و قم بإغلاقه ثم توجه إلى مجلد المشروع الخاص بك و ستجد أن هناك ملف xcode جديد ظهر لديك في المجلد.. حيث أن هذا هو نفس مشروعك الذي كنت تعمل به و لكن الفرق الوحيد أنه يضم كل المكتبات التي طلبت تنزيلها. و الآن لتتأكد أن التنزيل تم بطريقة صحيحة.. قم بفتح الملف الجديد و اذهب إلى أي كلاس و في أعلا الملف قم بكتابة أمر : import JTAppleCalender إذا لم يظهر البرنامج أي مشكلة فهذا يعني أنك قمت بتضمين المكتبة في مشروعك بنجاح. ملاحظة : قد يظهر الxcode خطأ في البداية عند كتابتك لهذا الأمر.. حينها قم بمسحه و قم بعمل build عن طريق cmd+b ثم اكتب الأمر مرة أخرى و يجب أن يعمل هذه المرة إلى هنا نصل إلى نهاية الدرس و إن كنت تود تجربة عمل الcalender و استخدام المكتبة فقم بمتابعة الفيديوهات الموجودة في رابط تنزيل المكتبة و قراءة التوجيهات الموجودة هناك. إلى اللقاء في دروس قريبة قادمة بإذن الله
  8. السلام عليكم ورحمة الله وبركاته.. في هذا الدرس الثالث من دروس النود جي اس سنقوم بتعلم كيفية ضبط ويب سيرفر NGINX للعمل مع تطبيقات / مايكرو سيرفس الNodeJS و تحويل requests الى بورتات تطبيقاتك بشكل ضمني ماهو الnginx؟ ببساطة هو بروكسي سيرفر عالي الاداء او ويب سيرفر، بديل الapache. في ماذا يستخدم؟ يستخدم في ريفيرس بروكسي للبروتوكولات HTTP, HTTPS, SMTP, IMAP, POP3 و كذلك لعمل توازن الضغط على السيرفر مثلا عندما يكون هناك عدد كبير جدا من الrequests تستطيع توزيع هذه الطلبات بالupstream بين عدة بورتات في نفس السيرفر او سيرفر خارجي مثلا اذا كان التطبيق او الموقع مبرمج بالNodeJS. بالعربي: لا تحاتي بتعرف كلشي بعد شوي. اولا: تثبيت الويب سيرفر. لتثبيت الويب سيرفر nginx اتبع التعليمات / الخطوات التالية: افتح التيرمنال وقم بإيقاف الapache اذا كان مثبت sudo /etc/init.d/apache stop حدث sudo apt-get update ثبت sudo apt-get install nginx تأكد ان الويب سيرفر يعمل sudo /etc/init.d/nginx status ملاحظة: مهم جدا ايقاف Apache قبل تثبيت Nginx ثانيا: تشغيل تطبيقك الNode على الدومين الرئيسي. لتشغيل التطبيق عليك تحويل جميع الطلبات requests التي تأتي من العميل الى الPort الذي يعمل عليه التطبيق (3000 مثلا) عبر الويب سيرفر NGINX. ولعمل ذلك، افتح التيرمنال و قم بمتابعة هذه الاوامر اذهب لمجلد الويب سيرفر cd /etc/ngin/sites-available اكتب ls وسترى ملف بإسم default. افتح الملف للتعديل بإستخدام vim او nano وفي هذا الدرس سنستخدم الاول sudo vim default امسح كلشي واكتب هذا الكود server { listen 80 default_server; listen [::]:80 default_server; server_name www.example.com example.com; access_log /path/to/log/static_domain_access.log; location / { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-NginX-Proxy true; proxy_pass http://127.0.0.1:3000; proxy_redirect off; } } اخيرا قم بإعادة تشغيل الويب سيرفر افتح المتصفح و اكتب موقعك http://www example.com Bonus إذا كان لديك تطبيق \ موقع كبير جدا او API و تريد عمل load balancing لتوزيع عمليه الطلبات في الصورتين ادناه الطريقة لذلك, لاحظ ان التطبيق يعمل على اكثر من بورت وكذلك الـproxy pass هو عباره عن اسم الـupstream .cluster تطبيق الNode كونفيقريشن الـNginx
  9. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته في هذا الدرس سنتعرف على احد أهم الأنواع في الSwift الا وهما Structures and Enumerations. أولا: Structures أو struct في Swift او اي لغة برمجة مشابهة، تعتبر الكلاسات حجر الأساس لمبدأ الObject-Oriented Programming. بالإضافة الى الكلاسات، لدينا الStructures التي تعطينا بديل مشابه للكلاسات. الستركتشر مشابه للكلاس، لأنه يمكن أن يحتوي على methods, properties, initializers تماما كما الكلاس، ولكن الفارق الأساسي هو ان الستركتشر يعتبر من نوع Value، والكلاسات تعتبر من نوع Reference. ما معنى Value type و Reference type؟ صورة توضيحية للفرق بين الRegerence والValue جميع الأنواع في الSwift هي اما عبارة عن Value type او Reference type. المتغيرات من نوع القيمة (Value type) -مثل متغيرات الInt والBool-، تقوم بنسخ قيمتها عندما يتم مساواتها بمتغير آخر بينما متغيرات نوع المرجع (Reference type) -مثل أي اوبجكت من كلاس-، تقوم باعارة قيمتها عندما يتم مساواتها بمتغير آخر. لنشرح الفرق بينهم سنطرح هذا المثال البسيط لنوع القيمة: var a: Int var b: Int a=5 b=a a=10 print(a) print(b) في المثال السابق، سيتم طباعة قيمتين مختلفتين لكل من a=10 و b=5، لأن قيمة a نسخت في البداية الى b حتى أصبح كل متغير يحمل قيمة خاصة ومستقلة عن المتغير الآخر، وعند تغيير قيمة a لم تتأثر قيمة b بذلك. ولنوع المرجع لنرى المثال التالي: var a = Car() var b: Car b = a a.startCar() b.printCarStatus() في المثال السابق، اذا طبقنا هذا الكود فسنلاحظ أن متغير السيارة b يعمل بالفعل، أي أن قيمة a اعيرت الى b، بحيث أي تغيير يطرأ على a سيؤثر بb، وأي تغيير يطرأ على b سيؤثر بa. اذا من المهم جدا أن نتذكر بأن الStructures من نوع القيمة (Value type)، بينما الClasses من نوع المرجع (Reference type). اختلاف آخر صغير بين الستركتشرز والكلاسات، أن الproperties في الستركتشر لا يمكن تعديلها بطريقة مباشرة من الميثودز، بل يجب استعمال كلمة mutator لكل ميثود كي نستطيع تعديل الproperties. مثال: struct Circle { var centerX = 0.0, centerY = 0.0, radius = 1.0 mutating func doubleRadius() { radius = radius * 2 } } اذا أردنا مضاعفة قيمة الradius من داخل الفنكشن doubleRadius، فسنضطر لاستخدام كلمة mutator قبل الميثود، دلالة على تغيير المتغير radius بقيمة أخرى دون الحاجة لنسخه. فيما عدا ذلك، فإن الستركتشرز تعتبر مشابهة تماما للكلاسات. ثانيا: Enumerations او enum التعدادات (Enumerations) هي طريقة نستطيع من خلالها أن نجمع العديد من قيم المتغيرات المرتبطة ببعضها. وكما الستركتشرز، فان التعدادات تعتبر من انواع القيمة (Value type). لنفترض بأننا نريد أن نعرف مجموعة الكواكب الشمسية، بدون استخدام التعدادات سنقوم بكتابة كود مشابه للتالي: let mercury = 1 let venus = 2 let earth = 3 ... وبهذه الحالة سنضطر لاضاعة أسطر أخرى للتأكد من الادخال الصحيح، فمثلا لو أدخل احدهم الرقم -1: var currentPlanet = -1 // not a valid planet! فسيكون هناك خطأ أنتاء عمل البرنامج، ان لم نقم بكتابة if-statements مناسبة ولكن في حالة التعدادات، يمكننا كتابتها كالتالي: enum Planet { case mercury = 1 case venus = 2 case earth = 3 case mars = 4 case jupiter = 5 case saturn = 6 case uranus = 7 } او اختصارا: enum Planet { case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune } والآن، ان تم ادخال قيمة غير صحيحة: var currentPlanet = Planet.mercury currentPlanet = -1 // error! فسنتعرف على الخطأ مباشرة. في هذا المثال سنرى امكانية تخزين قيم مختلفة لكل case لاحقا، الآن لننشئ مجموعة الألوان الخاصة بنا: enum Color { case rgb(Int, Int, Int) case argb(Int, Int, Int, Int) case cmyk(Int, Int, Int, Int) case name(String) } عند كتابة الenum بهذه الطريقة، فكأننا نخبر الSwift بأن كل case ستحصل على قيم خاصة بها لاحقا، مثل: var blue = Color.rgb(0, 0, 255) ويمكن استعمال الاختصار التالي عند تغيير قيمة المتغير: var blue = Color.rgb(0, 0, 255) blue = .argb(100, 0, 0, 255) blue = .name("blue") فبعد المساواة الأولى، أصبح من المعروف أن المتغير blue لن يخرج عن أحد خيارات المتعدد Color. وهنا نصل الى ختام موضوعنا، تقبل الله منا ومنكم صالح الأعمال والسلام عليكم ورحمة الله
  10. السلام عليكم ورحمة الله وبركاته.. في هذا الدرس الثالث من دروس النود جي اس سنقوم بتعلم كيفية ضبط ويب سيرفر NGINX للعمل مع تطبيقات / مايكرو سيرفس الNodeJS و تحويل requests الى بورتات تطبيقاتك بشكل ضمني ماهو الnginx؟ ببساطة هو بروكسي سيرفر عالي الاداء او ويب سيرفر، بديل الapache. في ماذا يستخدم؟ يستخدم في ريفيرس بروكسي للبروتوكولات HTTP, HTTPS, SMTP, IMAP, POP3 و كذلك لعمل توازن الضغط على السيرفر مثلا عندما يكون هناك عدد كبير جدا من الrequests تستطيع توزيع هذه الطلبات بالupstream بين عدة بورتات في نفس السيرفر او سيرفر خارجي مثلا اذا كان التطبيق او الموقع مبرمج بالNodeJS. بالعربي: لا تحاتي بتعرف كلشي بعد شوي. اولا: تثبيت الويب سيرفر. لتثبيت الويب سيرفر nginx اتبع التعليمات / الخطوات التالية: افتح التيرمنال وقم بإيقاف الapache اذا كان مثبت sudo /etc/init.d/apache stop حدث sudo apt-get update ثبت sudo apt-get install nginx تأكد ان الويب سيرفر يعمل sudo /etc/init.d/nginx status ملاحظة: مهم جدا ايقاف Apache قبل تثبيت Nginx ثانيا: تشغيل تطبيقك الNode على الدومين الرئيسي. لتشغيل التطبيق عليك تحويل جميع الطلبات requests التي تأتي من العميل الى الPort الذي يعمل عليه التطبيق (3000 مثلا) عبر الويب سيرفر NGINX. ولعمل ذلك، افتح التيرمنال و قم بمتابعة هذه الاوامر اذهب لمجلد الويب سيرفر cd /etc/ngin/sites-available اكتب ls وسترى ملف بإسم default. افتح الملف للتعديل بإستخدام vim او nano وفي هذا الدرس سنستخدم الاول sudo vim default امسح كلشي واكتب هذا الكود server { listen 80 default_server; listen [::]:80 default_server; server_name www.example.com example.com; access_log /path/to/log/static_domain_access.log; location / { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-NginX-Proxy true; proxy_pass http://127.0.0.1:3000; proxy_redirect off; } } اخيرا قم بإعادة تشغيل الويب سيرفر افتح المتصفح و اكتب موقعك http://www example.com Bonus إذا كان لديك تطبيق \ موقع كبير جدا او API و تريد عمل load balancing لتوزيع عمليه الطلبات في الصورتين ادناه الطريقة لذلك, لاحظ ان التطبيق يعمل على اكثر من بورت وكذلك الـproxy pass هو عباره عن اسم الـupstream .cluster تطبيق الNode كونفيقريشن الـNginx
  11. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته في هذا الدرس سنتعرف على احد أهم الأنواع في الSwift الا وهما Structures and Enumerations. أولا: Structures أو struct في Swift او اي لغة برمجة مشابهة، تعتبر الكلاسات حجر الأساس لمبدأ الObject-Oriented Programming. بالإضافة الى الكلاسات، لدينا الStructures التي تعطينا بديل مشابه للكلاسات. الستركتشر مشابه للكلاس، لأنه يمكن أن يحتوي على methods, properties, initializers تماما كما الكلاس، ولكن الفارق الأساسي هو ان الستركتشر يعتبر من نوع Value، والكلاسات تعتبر من نوع Reference. ما معنى Value type و Reference type؟ صورة توضيحية للفرق بين الRegerence والValue جميع الأنواع في الSwift هي اما عبارة عن Value type او Reference type. المتغيرات من نوع القيمة (Value type) -مثل متغيرات الInt والBool-، تقوم بنسخ قيمتها عندما يتم مساواتها بمتغير آخر بينما متغيرات نوع المرجع (Reference type) -مثل أي اوبجكت من كلاس-، تقوم باعارة قيمتها عندما يتم مساواتها بمتغير آخر. لنشرح الفرق بينهم سنطرح هذا المثال البسيط لنوع القيمة: var a: Int var b: Int a=5 b=a a=10 print(a) print(b) في المثال السابق، سيتم طباعة قيمتين مختلفتين لكل من a=10 و b=5، لأن قيمة a نسخت في البداية الى b حتى أصبح كل متغير يحمل قيمة خاصة ومستقلة عن المتغير الآخر، وعند تغيير قيمة a لم تتأثر قيمة b بذلك. ولنوع المرجع لنرى المثال التالي: var a = Car() var b: Car b = a a.startCar() b.printCarStatus() في المثال السابق، اذا طبقنا هذا الكود فسنلاحظ أن متغير السيارة b يعمل بالفعل، أي أن قيمة a اعيرت الى b، بحيث أي تغيير يطرأ على a سيؤثر بb، وأي تغيير يطرأ على b سيؤثر بa. اذا من المهم جدا أن نتذكر بأن الStructures من نوع القيمة (Value type)، بينما الClasses من نوع المرجع (Reference type). اختلاف آخر صغير بين الستركتشرز والكلاسات، أن الproperties في الستركتشر لا يمكن تعديلها بطريقة مباشرة من الميثودز، بل يجب استعمال كلمة mutator لكل ميثود كي نستطيع تعديل الproperties. مثال: struct Circle { var centerX = 0.0, centerY = 0.0, radius = 1.0 mutating func doubleRadius() { radius = radius * 2 } } اذا أردنا مضاعفة قيمة الradius من داخل الفنكشن doubleRadius، فسنضطر لاستخدام كلمة mutator قبل الميثود، دلالة على تغيير المتغير radius بقيمة أخرى دون الحاجة لنسخه. فيما عدا ذلك، فإن الستركتشرز تعتبر مشابهة تماما للكلاسات. ثانيا: Enumerations او enum التعدادات (Enumerations) هي طريقة نستطيع من خلالها أن نجمع العديد من قيم المتغيرات المرتبطة ببعضها. وكما الستركتشرز، فان التعدادات تعتبر من انواع القيمة (Value type). لنفترض بأننا نريد أن نعرف مجموعة الكواكب الشمسية، بدون استخدام التعدادات سنقوم بكتابة كود مشابه للتالي: let mercury = 1 let venus = 2 let earth = 3 ... وبهذه الحالة سنضطر لاضاعة أسطر أخرى للتأكد من الادخال الصحيح، فمثلا لو أدخل احدهم الرقم -1: var currentPlanet = -1 // not a valid planet! فسيكون هناك خطأ أنتاء عمل البرنامج، ان لم نقم بكتابة if-statements مناسبة ولكن في حالة التعدادات، يمكننا كتابتها كالتالي: enum Planet { case mercury = 1 case venus = 2 case earth = 3 case mars = 4 case jupiter = 5 case saturn = 6 case uranus = 7 } او اختصارا: enum Planet { case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune } والآن، ان تم ادخال قيمة غير صحيحة: var currentPlanet = Planet.mercury currentPlanet = -1 // error! فسنتعرف على الخطأ مباشرة. في هذا المثال سنرى امكانية تخزين قيم مختلفة لكل case لاحقا، الآن لننشئ مجموعة الألوان الخاصة بنا: enum Color { case rgb(Int, Int, Int) case argb(Int, Int, Int, Int) case cmyk(Int, Int, Int, Int) case name(String) } عند كتابة الenum بهذه الطريقة، فكأننا نخبر الSwift بأن كل case ستحصل على قيم خاصة بها لاحقا، مثل: var blue = Color.rgb(0, 0, 255) ويمكن استعمال الاختصار التالي عند تغيير قيمة المتغير: var blue = Color.rgb(0, 0, 255) blue = .argb(100, 0, 0, 255) blue = .name("blue") فبعد المساواة الأولى، أصبح من المعروف أن المتغير blue لن يخرج عن أحد خيارات المتعدد Color. وهنا نصل الى ختام موضوعنا، تقبل الله منا ومنكم صالح الأعمال والسلام عليكم ورحمة الله تم ترقية هذا الطرح المميز الى صفحة المقالات
  12. تعلمنا في ما سبق عن نوع من الـ Animation وهو الـ UIView.animate والذي يعتبر الأكثر استخداما واليوم سوف نتعلم عن نوع مختلف وهو UIView.transition قبل ان نبدأ في الدرس ، ما الفرق بينهما ؟ باختصار UIView.animate مسؤول عن ملكية الـ UIView من عمل تحريك وتكبير والتفاف وغيرها في حين UIView.transition مسؤول عن إضافة وحذف الـ View تستطيع عند إضافة الـ View كـ Subview انك تعمل Animation في لحظة اضافته او حذفه اعلم الكلام غير مفهوم ! مع التطبيق سوف تتضح الصورة . لتسهيل الامور لن اتطرق الى موضوع إضافة وحذف الـ SubView عن طريق الاكواد ولكن سوف اشرحها بطريقة اكثر بساطة . اذا لنبدأ الدرس . نقوم بإضافة Label ونجعل لون النص باللون الأبيض ولون الخلفية باللون الأسود ومن ثم نضيف زر كما في الصورة التالية : نقوم بإضافتهم الى ملف اكواد التطبيق ونجعل اسم الـ Label بـ Label واسم الـ Function بـ Transition @IBOutlet weak var Label: UILabel! @IBAction func Transition(_ sender: Any) { } الان في viewDidLoad نضيف التالي : Label.isHidden = true لماذا ؟ ذكرنا في السابق بأن الـ UIView.transition يعمل اثناء إضافة وحذف الـ View لذا في الكود السابق ما نقوم به هو حذفه من الـ View بجعله غير ظاهر ! في الدروس السابقة استخدمنا .alpha فما الفرق ؟ الـ .alpha هي الشفافية بمعنى 1 يعتبر لا توجد شفافية في حين 0.5 تعني وجود شفافية و 0 يعني انعدام الشفافية وهي من ملكيات الـ UIView.animate بصورة أخرى من المكيات التي يمكن عمل لها animate لكن الـ isHidden يزيل الـ View وليست من الملكيات التي تستطيع عمل لها animate فاذا استخدمتها بداخل اقواس UIView.animate لن يظهر أي Animation ! بصورة مختصرة : استخدام UIView.transition مع الملكيات التي لا يمكن عمل لها Animation نعود للدرس الان نقوم بعمل التالي بداخل اقواس Transition UIView.transition(with: Label, duration: 0.5, options: .transitionCurlDown, animations: { self.Label.isHidden = false }, completion: nil) كما تلاحظ بصورة عامة فهو مشابه للـ UIView.animate الاختلاف with وتعني ماهو الـ view الذي تريد عمل له الـ transition تكتب نفس اسم الـ View وهنا نحن استخدمنا Label فنقوم بكتابة اسم الـ Label الـ options هناك أنواع مختلف سوف اذكرها بعد قليل ما اريد توضيحه هنا الانواع التي ذكرتها في موضوعي السابق .curveEaseIn واخواتها يمكنك دمجها مع الانواع الخاصة بالـ transition بعمل مربع الاريه [] والفصل بينهم بعلامة فاصلة ماقمنا بفعله داخل الاقواس هو فقط حولنا الحالة من مخفي الى ظاهر بالتغير من true الى false أنواع الـ options : transitionCurlDown : وهو النوع الذي استخدمناه في الكود السابق transitionCurlUp : هيا عملية معاكسه للعملية السابقة لم استطيع عمل صورة متحركة توضحها transitionFlipFromTop: :transitionFlipFromBottom :transitionFlipFromRight :transitionFlipFromLeft transitionCrossDissolve: هناك نوع أخير وهو .showHideTransitionViews سيتضح فائدته في الـ Function الاخر للـ transition من الامور التي اتضحت من أنواع الـ Options السابقة بأنك قد ترغب باستخدام الـ transition لعمل تأثير معين لن تستطيع عمله باستخدام الـ UIView.animate ولكن من الامور الأخرى هو عمل تأثير انتقالي وسيكون المثال التالي توضيحا للطريقة قبل العوده لاستكمال الدرس هنا تلميحه بسيطة وهي هل تريد مشاهدة التأثير ببطيء ؟ عند تشغيل التطبيق على المحاكي اذهب الى خانة Debug واختار خيار Slow Animations ومن ثم اضغط على الزر وسظهر الـ Animation بصورة بطيئة (سلو موشن) كما في الصورة التالية : مثال : نعود للدرس الان سوف ننتقل الى نوع أخرى من أنواع الـ UIView.transition UIView.transition(from: , to: , duration: , options: , completion: nil) كما تلاحظ في الكود السابق بأنه يطلب نوعين من الـ UIView ، وهذا النوع هو نوع انتقالي بحيث يعمل عملية انتقالية بين الـ View الاول الى الـ View الثاني بحيث يخفي الاول ويظهر الثاني ! دعونا نبدأ في المثال أولا : نذهب الى الـ Storyboard ونضيف التالي : نضيف UIView ونجعله بحجم مستطيل صغير ومن ثم بداخله نضيف UIView اخر ونجعله بنفس حجم الـ UIView السابق ونغير لونه الى الاخضر ومن ثم نضيف Label اذا اردت وأيضا نضيف Button ، نحذف النص الذي بداخله ونجعله بحجم الـ UIView كما في الصورة التالية : ومن ثم نقوم بنسخ الـ UIView الذي قمنا بعمله في الخطوة السابقة ونغير النص ولون الـ UIView الى الازرق كما في الصورة التالية : الان اصبح لدينا ثلاثة من الـ UIView اثنين بداخل UIView واحد وفوق كل UIView زر بما يعني ٣ من الـ UIView و ٢ من الـ Button قد تتسأل لماذا اضفنا UIView وبداخله (فوقه) اضفنا اثنين من الـ UIView بدلا من إضافة اثنين من الـ UIView مباشرةً الى الـ ViewController؟ السبب لأنه عند عمل الـ transition سنقلب الـSuperview الذي سيكون الـ View الأساسي بمعنى سوف تنقلب كامل الصفحة !! لكن عند إضافة UIView وجعلنا اثنين من الـ UIView بداخله الذي سوف ينقلب هو الـ UIView الي اضفناه لأنه اصبح هو الـSuperview بالنسبة لهم بما يعني لن تنقلب الصفحة كامله ! بما يعطي ايحاء بتأثير انقلاب البطاقة ! ملاحظة : في الـ StoryBoard من يكون في اخر التسلسل يكون هو الاول (الظاهر) فالان اصبح الـ View 2 هو الاول والـ View 1 هو الثاني يمكنك تغيير المسميات او فقط تقوم بسحب الـ View 2 وتجعله فوق View 1 بالشكل التالي : ملاحظة ٢ : عندما يكون هناك نوعين مختلفة فوق بعض (في المثال هذا هناك اثنين من الـ View) ، سوف يكون من الصعب مشاهدة الـ View الذي في الخلف ، اذا طبقت الملاحظة الاولى بعد عمل القيود سوف تواجه مشاكل مع القيود ! اذا ما الحل ؟ كل ما عليك فعله هو الضغط على الـ View الذي في المقدمة (الذي يكون الأخير في الترتيب) ومن ثم إزالة علامة الصح من Installed وعندها ستخفي وسيظهر الـ View الذي كان في الخلف وسيسهل عليك تعديله بعد الانتهاء من التعديلات قم بتفعيل الصح مره أخرى ! صورة توضيحية : الان نقوم بإضافتهم الى ملف الاكواد سنقوم بإضافة فقط الـ UIView الذي باللون الأزرق والاخضر وأيضا سوف نضيف Action للزر ونربط الزرين بهذا الـ Action بصيغة أخرى سوف يصبح لدينا Function واحد وزرين مرتبطين به ! لأننا نريد جعل البطاقة تنقلب مرتين عند اللمس عوضا عن عمل اثنين من الـ Function سوف نعمل على Function واحد فقط كما في الصورة التالية : وبالتالي ملف الاكواد سوف يصبح بالشكل التالي : import UIKit class ViewController: UIViewController { @IBOutlet weak var View1: UIView! @IBOutlet weak var View2: UIView! override func viewDidLoad() { super.viewDidLoad() } @IBAction func FlipButton(_ sender: UIButton) { } الان بداخل الـ FlipButton نقوم بكتابة التالي : @IBAction func FlipButton(_ sender: UIButton) { UIView.transition(from: self.View1, to: self.View2, duration: 0.5, options: [.transitionFlipFromRight , .showHideTransitionViews], completion: nil) } ما قمنا به هو جعلنا الـ View1 ينقلب الى View2 لاحظ اننا استخدمنا الـ showHideTransitionViews لماذا ؟ لانه بعد عملية الانتقال سوف يتم حذف الـ View1 من الـ Suberview وبالتالي سوف يسبب بخطأ nil والخيار هذا بدل من ازالت الـ View1 سوف يقوم بإخفائه فقط الان عند التشغيل سوف تلاحظ بانه ينقلب من View1 الى View2 ومن ثم لن يعود الى View1 بل سوف يستمر بعرض View2 صورة توضيحية بالنتيجة الحالية : لماذا لم يعود الى الـ View الاول ؟ لأنه الكود الذي كتبناه يقوم على تحويل من View1 الى View2 بما يعني دائما سوف يظهر View2 باستطاعتنا تعديل الخطأ بإضافة Boolean var check = false اسفل @IBOutlet weak var View1: UIView! @IBOutlet weak var View2: UIView! ومن ثم نغير الكود الى هذا الشكل @IBAction func FlipButton(_ sender: UIButton) { check = !check let fromView = check ? View1 : View2 let toView = check ? View2 : View1 UIView.transition(from: fromView!, to: toView!, duration: 0.5, options: [.transitionFlipFromRight , .showHideTransitionViews], completion: nil) } ما الذي قمنا به هنا ؟ قمنا بتغير حالة الـ Boolean اذا كان false سوف يصبح true والعكس صحيح ملاحظة : علامة التعجب هنا معناها اعكس القيمة! ومن ثم اضفنا متغير باسم fromView واضفنا شرط اذا كان true اجعله View1 اذا كان false اجعله View2 واضفنا متغير اخر باسم toView واضفنا شرط اذا كان true اجعله View2 اذا كان false اجعله View1 ومن ثم اضفنا المتغيرات الجديدة الى from و to بداخل Function الـ UIView.transitio اعلم بانه البعض سوف يرى بأن طريقة كتابة الـ IF غريبة وغير منطقية بالنسبة له لذا لتبسيط الامور يمكن أيضا كتابتها بالطريقة التالية : @IBAction func FlipButton(_ sender: UIButton) { check = !check var formView : UIView if check == true { formView = View1 }else { formView = View2 } var toView : UIView if check == true { toView = View2 }else { toView = View1 } UIView.transition(from: formView, to: toView, duration: 0.5, options: [.transitionFlipFromRight , .showHideTransitionViews], completion: nil) } صورة توضيحية بالنتيجة :
  13. شكراً عالم البرمجة على دعمكم المستمر
  14. مجهود تشكر عليه ، جزاك الله كل خير
  15. السلام عليكم و رحمة الله و بركاته .. عودة الى الجزء الاول، وضحت كيفية ارسال بيانات من تطبيق الNode الى Pug وكيفية استقبال البيانات في الطرف الاخر و عرضها. في هذا الدرس سنتعامل مع قاعدة البيانات ونرسل البيانات بطريقتين مختلفتين: سيتم ارسال جميع البيانات من احد الجداول الى الواجهة و سأعرض لكم طريقة التعامل مع هذا النوع من البيانات المرسله سننشأه items عباره عن json objects وبعدها نرسلهم. الطريقة الأولى: و بالحديث عن قواعد البيانات، سنستخدم قاعدة بيانات MySQL هذه المره ( الرجاء الرجوع للدرس الاول للحصول على كافة التجهيزات لهذا الدرس). / هنا , انشأ قاعدة بيانات بإسم company وجدول employees مثلا ومن ثم اضف هذه المدخلات. -- phpMyAdmin SQL Dump -- version 4.5.4.1deb2ubuntu2 -- http://www.phpmyadmin.net -- -- Host: localhost -- Generation Time: Jun 09, 2017 at 10:41 AM -- Server version: 5.7.18-0ubuntu0.16.04.1 -- PHP Version: 7.1.5-1+deb.sury.org~xenial+2 SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; SET time_zone = "+00:00"; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8mb4 */; -- -- Database: `company` -- -- -------------------------------------------------------- -- -- Table structure for table `employees` -- CREATE TABLE `employees` ( `ID` int(11) NOT NULL, `name` text COLLATE utf8_unicode_ci NOT NULL, `position` text COLLATE utf8_unicode_ci NOT NULL, `office` text COLLATE utf8_unicode_ci NOT NULL, `age` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- -- Dumping data for table `employees` -- INSERT INTO `employees` (`ID`, `name`, `position`, `office`, `age`) VALUES (1, 'Airi Satou', 'Accountant', 'Tokyo', 23), (2, 'Airi Satou', 'Accountant', 'Tokyo', 23), (3, 'Donna Snider', 'Customer Support', 'New York', 27), (4, 'Brenden Wagner', 'Software Engineer', 'San Francisco', 28), (5, 'Caesar Vance', 'Pre-Sales Support', 'New York', 23), (6, 'Cedric Kelly', 'Senior Javascript Developer', 'Edinburgh', 22), (7, 'Dai Rios', 'Personnel Lead', 'Edinburgh', 35); -- -- Indexes for dumped tables -- -- -- Indexes for table `employees` -- ALTER TABLE `employees` ADD PRIMARY KEY (`ID`); -- -- AUTO_INCREMENT for dumped tables -- -- -- AUTO_INCREMENT for table `employees` -- ALTER TABLE `employees` MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=8; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; افتح ملف app.js و انشأ هذا الاوبجكت لعمل connection بين التطبيق وقاعدة البيانات // connection var connection = mysql.createConnection({ host : 'localhost', user : '...', password : '...', database : 'db_name' }); بعد ذلك، سنقوم بتعديل صفحة الـdetails في ملف app.js - المسار الذي يعرض صفحة الـdetails - لعمل Selection query app.get('/details', function(req, res) { connection.query('SELECT * From `employees`', function (error, results, fields) { if (error) throw error; console.log('The solution is: ', results); res.render('details', { title: 'Details - Pug ExpressJS NodeJS Tutorial', employees: results }); }); }); الـsyntax بسيط جدا، اولا connection وهو متغير التوصيل مم ثم query ثابته بعد ذلك الكويري &nbsp;ومن ثم الارقيومنتس (لمعلومات اكثر تجدونها هنا https://github.com/mysqljs/mysql/blob/master/Readme.md ) مخرجات اي query دائما ما يكون json object بالتالي متغير results هو عباره عن list of objects [ { "prop1":"value1", "prop2":"value2" }, { "prop1":"value3", "prop2":"value4" } ] نرجع إلى ملف details.pug ونقوم بتعديل block الجدول إلى التالي table#example.display.nowrap.dataTable.dtr-inline.collapsed(cellspacing='0') thead tr(role='row') th.sorting(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Name: activate to sort column ascending', style='width: 142px;') Name th.sorting(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Position: activate to sort column ascending', style='width: 192px;') Position th.sorting(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Office: activate to sort column ascending', style='width: 89px;') Office th.dt-body-right.sorting_asc(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Age: activate to sort column descending', style='width: 37px;', aria-sort='ascending') Age tfoot tr th(rowspan='1', colspan='1') Name th(rowspan='1', colspan='1') Position th(rowspan='1', colspan='1') Office th.dt-body-right(rowspan='1', colspan='1') Age tbody for employee in employees tr.odd(role='row') td #{employee.name} td #{employee.position} td #{employee.office} td.dt-body-right.sorting_1 #{employee.age} نلاحظ وجود الـfor loop و طريقة استقبال وعرض المعلومات بسطر واحد فقط و ترجمته حرفيا ان لكل موظف في اوبجكت الموظفين ( الذي ارسلناه من الـNode ) قم بعمل صف في الجدول واضف مابعده for employee in employees tr.odd(role='row') td #{employee.name} td #{employee.position} td #{employee.office} td.dt-body-right.sorting_1 #{employee.age} بهذا انتهينا من الجزء الأول للدرس وننتقل للجزء الثاني: هذا الجزء من الدرس يتعلق بكيفية عمل items بشكل تخصيصي .. ان ماذا لو انك لا تريد ان تضم عمر الموظف في جدول العرض؟ سنقوم بالتالي في ملف app.js و details.pug - وكلشئ آخر يبقى كما هو: //app.js app.get('/details', function (req, res) { connection.query('SELECT * From `employees`', function (error, results, fields) { if (error) { throw error; } else { console.log('The solution is: ', results); var employees_json = []; for (var index = 0; index < results.length; index++) { var item = { name: results[index].name, position: results[index].position, office: results[index].office } employees_json.push(item); } res.render('details', { title: 'Details - Pug ExpressJS NodeJS Tutorial', employees: employees_json }); } }); }); //details.pug table#example.display.nowrap.dataTable.dtr-inline.collapsed(cellspacing='0') thead tr(role='row') th.sorting(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Name: activate to sort column ascending', style='width: 142px;') Name th.sorting(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Position: activate to sort column ascending', style='width: 192px;') Position th.sorting(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Office: activate to sort column ascending', style='width: 89px;') Office //th.dt-body-right.sorting_asc(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Age: activate to sort column descending', style='width: 37px;', aria-sort='ascending') Age tfoot tr th(rowspan='1', colspan='1') Name th(rowspan='1', colspan='1') Position th(rowspan='1', colspan='1') Office th.dt-body-right(rowspan='1', colspan='1') Age tbody for employee in employees tr.odd(role='row') td #{employee.name} td #{employee.position} td #{employee.office} //td.dt-body-right.sorting_1 #{employee.age} إضغط هنا لتحميل ملفات الدرس الثاني anwbh-app-lesson2.zip
  16. السلام عليكم و رحمة الله و بركاته .. عودة الى الجزء الاول، وضحت كيفية ارسال بيانات من تطبيق الNode الى Pug وكيفية استقبال البيانات في الطرف الاخر و عرضها. في هذا الدرس سنتعامل مع قاعدة البيانات ونرسل البيانات بطريقتين مختلفتين: سيتم ارسال جميع البيانات من احد الجداول الى الواجهة و سأعرض لكم طريقة التعامل مع هذا النوع من البيانات المرسله سننشأه items عباره عن json objects وبعدها نرسلهم. الطريقة الأولى: و بالحديث عن قواعد البيانات، سنستخدم قاعدة بيانات MySQL هذه المره ( الرجاء الرجوع للدرس الاول للحصول على كافة التجهيزات لهذا الدرس). / هنا , انشأ قاعدة بيانات بإسم company وجدول employees مثلا ومن ثم اضف هذه المدخلات. -- phpMyAdmin SQL Dump -- version 4.5.4.1deb2ubuntu2 -- http://www.phpmyadmin.net -- -- Host: localhost -- Generation Time: Jun 09, 2017 at 10:41 AM -- Server version: 5.7.18-0ubuntu0.16.04.1 -- PHP Version: 7.1.5-1+deb.sury.org~xenial+2 SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; SET time_zone = "+00:00"; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8mb4 */; -- -- Database: `company` -- -- -------------------------------------------------------- -- -- Table structure for table `employees` -- CREATE TABLE `employees` ( `ID` int(11) NOT NULL, `name` text COLLATE utf8_unicode_ci NOT NULL, `position` text COLLATE utf8_unicode_ci NOT NULL, `office` text COLLATE utf8_unicode_ci NOT NULL, `age` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- -- Dumping data for table `employees` -- INSERT INTO `employees` (`ID`, `name`, `position`, `office`, `age`) VALUES (1, 'Airi Satou', 'Accountant', 'Tokyo', 23), (2, 'Airi Satou', 'Accountant', 'Tokyo', 23), (3, 'Donna Snider', 'Customer Support', 'New York', 27), (4, 'Brenden Wagner', 'Software Engineer', 'San Francisco', 28), (5, 'Caesar Vance', 'Pre-Sales Support', 'New York', 23), (6, 'Cedric Kelly', 'Senior Javascript Developer', 'Edinburgh', 22), (7, 'Dai Rios', 'Personnel Lead', 'Edinburgh', 35); -- -- Indexes for dumped tables -- -- -- Indexes for table `employees` -- ALTER TABLE `employees` ADD PRIMARY KEY (`ID`); -- -- AUTO_INCREMENT for dumped tables -- -- -- AUTO_INCREMENT for table `employees` -- ALTER TABLE `employees` MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=8; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; افتح ملف app.js و انشأ هذا الاوبجكت لعمل connection بين التطبيق وقاعدة البيانات // connection var connection = mysql.createConnection({ host : 'localhost', user : '...', password : '...', database : 'db_name' }); بعد ذلك، سنقوم بتعديل صفحة الـdetails في ملف app.js - المسار الذي يعرض صفحة الـdetails - لعمل Selection query app.get('/details', function(req, res) { connection.query('SELECT * From `employees`', function (error, results, fields) { if (error) throw error; console.log('The solution is: ', results); res.render('details', { title: 'Details - Pug ExpressJS NodeJS Tutorial', employees: results }); }); }); الـsyntax بسيط جدا، اولا connection وهو متغير التوصيل مم ثم query ثابته بعد ذلك الكويري &nbsp;ومن ثم الارقيومنتس (لمعلومات اكثر تجدونها هنا https://github.com/mysqljs/mysql/blob/master/Readme.md ) مخرجات اي query دائما ما يكون json object بالتالي متغير results هو عباره عن list of objects [ { "prop1":"value1", "prop2":"value2" }, { "prop1":"value3", "prop2":"value4" } ] نرجع إلى ملف details.pug ونقوم بتعديل block الجدول إلى التالي table#example.display.nowrap.dataTable.dtr-inline.collapsed(cellspacing='0') thead tr(role='row') th.sorting(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Name: activate to sort column ascending', style='width: 142px;') Name th.sorting(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Position: activate to sort column ascending', style='width: 192px;') Position th.sorting(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Office: activate to sort column ascending', style='width: 89px;') Office th.dt-body-right.sorting_asc(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Age: activate to sort column descending', style='width: 37px;', aria-sort='ascending') Age tfoot tr th(rowspan='1', colspan='1') Name th(rowspan='1', colspan='1') Position th(rowspan='1', colspan='1') Office th.dt-body-right(rowspan='1', colspan='1') Age tbody for employee in employees tr.odd(role='row') td #{employee.name} td #{employee.position} td #{employee.office} td.dt-body-right.sorting_1 #{employee.age} نلاحظ وجود الـfor loop و طريقة استقبال وعرض المعلومات بسطر واحد فقط و ترجمته حرفيا ان لكل موظف في اوبجكت الموظفين ( الذي ارسلناه من الـNode ) قم بعمل صف في الجدول واضف مابعده for employee in employees tr.odd(role='row') td #{employee.name} td #{employee.position} td #{employee.office} td.dt-body-right.sorting_1 #{employee.age} بهذا انتهينا من الجزء الأول للدرس وننتقل للجزء الثاني: هذا الجزء من الدرس يتعلق بكيفية عمل items بشكل تخصيصي .. ان ماذا لو انك لا تريد ان تضم عمر الموظف في جدول العرض؟ سنقوم بالتالي في ملف app.js و details.pug - وكلشئ آخر يبقى كما هو: //app.js app.get('/details', function (req, res) { connection.query('SELECT * From `employees`', function (error, results, fields) { if (error) { throw error; } else { console.log('The solution is: ', results); var employees_json = []; for (var index = 0; index < results.length; index++) { var item = { name: results[index].name, position: results[index].position, office: results[index].office } employees_json.push(item); } res.render('details', { title: 'Details - Pug ExpressJS NodeJS Tutorial', employees: employees_json }); } }); }); //details.pug table#example.display.nowrap.dataTable.dtr-inline.collapsed(cellspacing='0') thead tr(role='row') th.sorting(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Name: activate to sort column ascending', style='width: 142px;') Name th.sorting(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Position: activate to sort column ascending', style='width: 192px;') Position th.sorting(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Office: activate to sort column ascending', style='width: 89px;') Office //th.dt-body-right.sorting_asc(tabindex='0', aria-controls='example', rowspan='1', colspan='1', aria-label='Age: activate to sort column descending', style='width: 37px;', aria-sort='ascending') Age tfoot tr th(rowspan='1', colspan='1') Name th(rowspan='1', colspan='1') Position th(rowspan='1', colspan='1') Office th.dt-body-right(rowspan='1', colspan='1') Age tbody for employee in employees tr.odd(role='row') td #{employee.name} td #{employee.position} td #{employee.office} //td.dt-body-right.sorting_1 #{employee.age} إضغط هنا لتحميل ملفات الدرس الثاني anwbh-app-lesson2.zip
  17. السلام عليكم و رحمة الله و بركاته.. في هذا الموضوع سنتحدث عن عن علاقة إطار العمل ExpressJS مع الباترن NodeJS و كيفية عمل Pug as template engine اولا: حتى لا تكون هناك امور مبهمه و ابعاد الغيوم التي تدور حول اذهان الكثير من يريد العمل او التوجه لهذا الباترن يجب عرض بعض من الحقائق: الـNodeJS ليست لغة جديدة .. انما بيئة مبرمجة بالـJS. هذه البيئه تعمل في جهة السيرفر. ان كنت مبرمج AngularJS or ReactJS ليس هناك سطر محدد تربط فيه بين تطبيقك الNode مع الفريموورك الذي تعمل به. طريقة الربط تكون من خلال HTTP requests. تستطيع التعامل مع قواعد البيانات المختلفه مباشره بالـNode مثل MySQL, MariaDB و اشهرهم في هذا المجال هي MongoDB. نعود مرة اخرى للـExpressJS الذي هو يعتبر فريم وورك ابعد مما ان يقال عنه "مساعد" في تطبيقات الـNode. بإستخدام هذا الفريم وورك الذي يعمل كذلك في السيرفر، يتم تسهيل العمل و كمية الكود في تطبيق الـNode بشكل كبير جدا. مثال على تطبيق Node بإستخدام فريم وورك ExpressJS وسيتم شرح اجزائه جميعها var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('Hello World!') }); app.listen(3000, function () { console.log('Example app listening on port 3000!') }); اول سطرين استدعاء موديول الاكسبرس و من ثم دالة الاكسبرس في المتغير app الذي استخدمناه في صنع التطبيق. الشطر الثاني get هو عبارة عن نوع الـhttp request قد يكون GET, POST, DELETE, UPDATE etc …. بعد ذلك يأتي المسار - لتفاصيل اكثر حول هذا الجزء الرجاء زيارة الدرس السابق. نننقل الآن الى امر اخر في الـExpressJS وهو الـtemplating ( عمل الواجهات UI و استقبال البيانات المرسلة من الـNode ) لعمل الواجهات الديناميكيه يفضل استخدام محركات التمبليت التي تتوافق مع الاكسبرس وهي PUG - know as Jade Mustache EJS و في هذا الدرس سنستخدم المحرك الاول لعمل صفحة ويب اعتيادية و اخرى ديناميكية, لصنع واجهة بمحرك التمبليت pug يجب عليك ان تتقن او ان تاخذ فكره عن هذا المحرك لتعرف كيفية كتابه الكود ومثال على ذلك doctype html html(lang='en') head title Jade script(type='text/javascript'). foo = true; bar = function () {}; if (foo) { bar(1 + 5) } body h1 Jade - node template engine #container.col p You are amazing p | Jade is a terse and simple | templating language with a | strong focus on performance | and powerful features. يعادل في الHTML <!DOCTYPE html> <html lang="en"> <head> <title>Jade</title> <script type="text/javascript"> foo = true; bar = function () {}; if (foo) { bar(1 + 5) } </script> </head> <body> <h1>Jade - node template engine</h1> <div id="container" class="col"> <p>You are amazing</p> <p> Jade is a terse and simple templating language with a strong focus on performance and powerful features. </p> </div> </body> </html> لمعلومات اكثر حول اللغة اطلع على موقعهم الرسمي وبذلك سنقوم بإنشاء مجلدات ومستندات بهذه الطريقة حمل الـbootstrap وانقل كل من bootstrap.min.css إلى مجلد css , انقل bootstrap.min.js إلى مجلد js بعد ذلك إبدأ بتثبيت الـpackages التي سنعمل بها من خلال فتح الـterminal في مجلد myapp من خلال الاوامر التالية $ npm install express --save $ npm install cookie-parser --save $ npm install body-parser --save $ npm install mysql --save افتح ملف التطبيق app.js وقم بإستدعاء هذه البكجات var express = require('express'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var mysql = require('mysql'); الآن نبدأ بإعداد config فريم وورك الـExpressJS مع cookie-parser body-parser ( سنستخدمهم لاحقا ) var router = express.Router(); var app = express(); app.use(bodyParser.json({limit: "50mb"})); app.use(cookieParser()); var urlencodedParser = bodyParser.urlencoded({ extended: true, parameterLimit:50000 }); app.use("/", router); نضيف امكانية استخدام ملفات الـcss, js , images etc من خلال استخدام الـmiddleware ـexpress static بإعطاء مسار وهمي لهذه الملفات app.use('/css', express.static(__dirname + '/public/css')); app.use('/js', express.static(__dirname + '/public/js')); نرى وجود ملفات الـcss and js في مجلد public/css ولكن المسار الذي سنكتبه في الكود هو فقط css/ وليس بالضرورة ان يكون اسم المسار الوهمي ذو علاقه بالمسار الاساسي .. تستطيع ان تسميه مثلا sambosa/ و في الحقيقة هو public/css/ تحديد نوع الـtemplate engine الذي سنستخدمه في هذا التطبيق مع تحديد مسار مجلد الـviews الذي يحتوي على صفحات الموقع او التطبيق app.set('views', __dirname + '/views'); app.set('view engine', 'pug'); بعد إنشاء ملفات الـpug في إحدى الخطوات السابقه , انسخ والصق كود الصفحتين index.pug and layout.pug details.pug كلن على حدى //layout.pug doctype html html head title= title script(src='https://code.jquery.com/jquery-3.2.1.min.js') link(rel='stylesheet', href='/css/bootstrap.min.css') script(src='/js/bootstrap.min.js') body nav.navbar.navbar-default .container .navbar-header button.navbar-toggle.collapsed(type='button', data-toggle='collapse', data-target='#navbar', aria-expanded='false', aria-controls='navbar') span.sr-only Toggle navigation span.icon-bar span.icon-bar span.icon-bar a.navbar-brand(href='/') NodeJS #navbar.navbar-collapse.collapse ul.nav.navbar-nav li.active a(href='/') Home li a(href='/details') details li.dropdown a.dropdown-toggle(href='#', data-toggle='dropdown', role='button', aria-haspopup='true', aria-expanded='false') | Dropdown span.caret ul.dropdown-menu li a(href='#') Action li a(href='#') Another action li a(href='#') Something else here li.divider(role='separator') li.dropdown-header Nav header li a(href='#') Separated link li a(href='#') One more separated link block content //index.pug extends layout block content .container h1 #{msg} p #{username} , welcome to the first templating tutorial //details.pug extends layout block content .container .table-responsive h2 user table p This is all users we have so far table.table.table-bordered#example thead tr th Firstname th Lastname th Email tbody tr td John td Doe td john@example.com tr td Mary td Moe td mary@example.com tr td July td Dooley td july@example.com link(rel='stylesheet', type='text/css', href='https://cdn.datatables.net/v/dt/dt-1.10.15/datatables.min.css') script(type='text/javascript', src='https://cdn.datatables.net/v/dt/dt-1.10.15/datatables.min.js') script(). $(document).ready(function() { $('#example').DataTable(); }); ملاحظة: نستخدم extends layout لكي لا نكرر الـhead في الصفحات الاخرى , مثل include في الـPHP ( في هذا الدرس ) - الاستخدام الحقيقي للـlayout هو تصميم شكل معين لصفحات معينه .. كـtemplate خاص مثلا - معلومات اكثر من خلال الرابط هنا الأن تم تجهيز المستوى الأول من التطبيق وهو الـconfigurations , ننتقل إلى النص الآخر وهو الـroutes . اولا route الصفحة الرئيسية سيكون كالتالي app.get('/', function(req, res) { res.render('index', { title: 'Pug ExpressJS NodeJS Tutorial', username: 'abdulla', msg: 'Hey There!' }) }); نلاحظ ان مسار الـroute هو مجرد slash للامام .. بذلك يتم تحديد المسار الرئيسي للتطبيق, بعد ذلك res.render اي تعني response render حيث يقوم تطبيق الـNode بالأجابة من بعد عمل طلب request من طرف المستخدم , للتوضيح اكثر الـHTTP هو عباره عن بروتوكول طلب و اجابة وبذلك request and response - req, res فعند طلب العميل امر ما يجب عليه ان يحصل على شي ما في المقابل. و كما موضع في الدالة اعلاه ان الـresponse هو عباره عن عمل render لملف index.pug الموجود في مجلد views مع ارسال ثلاث متغيرات title, username and msg , و طريقة استقبال البيانات في الـpug هي كالتالي ( يوجد اكثر من طريقة ) h1 #{username} p Welcome to #{title} خطوة اخيره لتشغيل التطبيق يجب تحديد بورت معين ولنفترض 3000 app.listen(3000, function() { console.log('The app is running http://localhost:3000'); }); افتح الـterminal داخل مجلد التطبيق myapp واكتب الامر التالي لتشغيل التطبيق node app.js افتح المتطفح على المسار انتهى الدرس الاول .. تم من خلاله عمل configuration لتطبيق الـnode الذي سنستخدمه في الدروس القادمة تثبيت template engine و توصيله مع تطبيق الـNode ارسال بيانات من الـNode إلى pug الدرس القادم سيكون هناك سنعرض لكم كيفية التعامل مع قواعد البيانات وإرسال المعلومات الى pug للحصول على ملفات العمل تجدونهم في المرفقات, بعد تحميل الملف قم بفك الضغط انتقل إلى داخل المجلد واكتب الأمر التالي لتثبيت جميع الـpackages دفعة واحده $ npm install --save أضغط هنا لتحميل ملفات العمل anwbh-app.zip موقع مساعد لتحويل كودات الـHTML إلى Pug http://html2jade.org
  18. بسم الله الرحمن الرحيم كيف تحمي بيانات المستخدمين من الإختراق عن طريق 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 خانات. كما نلاحظ تتمت الإزاحة 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. كما نلاحظ في الشكل السابق أن النص الناتج من عملية الـ Hashing نص ثابت الطول (10) خانات مهما أختلف طول النص في النص الأصلي في الجهة اليسار. أيضا نلاحظ أن 123 كانت عملية الـ Hashing لها واحدة (Fdeom83nyU) مهما تكررت يكون الناتج واحد. خوارزميات الـ Hashing: يوجد العديد من خوارزميات الـ Hashing من أشهرها: · MD5 Message Digest Algorithm 5 · SHA-1,SHA-2,SHA-3 Secure Hash Algorithm · BLAKE and BLAKE2 كيف يتم كسر كلمات المرور؟ في البداية يمكن إكتشاف كلمة المرور دون النظر إلى كيفية تخزينها في قاعدة البيانات هل هي بنص واضح أو مشفرة أو تم عمل 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 ( بالتأكيد لا يعرفها المخترق). ستتم هذه الطريقة حسب الملف الذي يمتلكه المخترق (الشكل بالأسفل) ستتم تجربة كلمات الملف كالتالي 2- Brute Force Attack هذه الطريقة شبيهه بالطريقة السابقة حيث أنها تعتمد أيضا على التخمين ولكن التخمين بالاعتماد على عمليات حسابية حيث يتم تجربة كل الاحتمالات الممكنة مثلا تجربة كل الاحتمالات لكلمة مرور ذات طول 6 خانات عبارة عن أرقام أو أحرف. فهنا الموضوع لا يعتمد على جدول أو ملف إنما تجربة أثناء وقت التنفيذ. وهذه الطريقة تأخذ وقت طويل يزيد وينقص حسب طول كلمة المرور. مثال : لنفرض أن المخترق لديه هذا الحساب User@mail.com وكلمة المرور هي 111333 ( بالتأكيد لا يعرفها المخترق). تعمل هذه الطريقة بهذا الشكل: سيتم تجربة كل الاحتمالات الممكنة حتى يصل إلى كلمة المرور. وقد تستمر إلى وقت طويل جداً حتى يتم إيجاد كلمة المرور. أيضا هنا تم استخدام (Online Attack) حيث أن المخترق يرسل للخادم في كل مرة اسم المستخدم مع احتمال لكلمة المرور بشكل متسلسل. ويمكن استخدام الـ (Offline Attack) إذا كانت كلمة المرور محفوظة كـ Hash حيث يكون لدى المخترق الـ Hashed Password فيمكن اكتشاف كلمة المرور بنفس الطريقة ولكن يتم أولا عمل Hash أُثناء التنفيذ لكل سلسلة من الحروف ثم مقارنتها مع الـ Hashed Password. لنفرض أن المخترق لديه هذه الكلمة (Hashed Password) التالية: 62b37844f7adeb0df6f180126327dca339fb7003 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 ) وليس أثناء وقت التنفيذ. فالعملية هي عملية بحث أو استعلام عادية للحصول على الـ Hash الموجودة في الجدول والمطابقة لما لدى المخترق (e9989db5dabeea617f40c8dbfd07f5fb). وهنا تكون كلمة المرور الأصلية هي Car. وطبعا هذه الطريقة تختصر الوقت لكن تعتمد كفائتها على كمية الكلمات الموجودة في الجدول. يوجد بعض البرامج التي تقوم بإنشاء هذه الجداول حسب بعض المعطيات لدى المخترق ( أي لا يشترط أن يكون هناك قائمة جاهزة للكلمات المشهورة إنما يمكن إنشائها) من هذه المعطيات أن يتم إنشاء جدول (Lookup tables) للكلمات ذات الطول من 1 إلى 7 خانات وتكون عبارة عن أرقام فقط. ويمكن أن تكون أرقام أو حروف أو رموز. كلما زادت المعطيات زاد وقت إنشاء هذه الجداول وزادت مساحة التخزين. 4- Rainbow Tables هو نوع مخصص من Lookup table بنفس الفكرة والاعتماد على (precomputation ). ولكن تعمل تحت مبدأ (space/time trade-off) وهذا يعني زيادة مساحة التخزين في سبيل تقليل وقت التنفيذ أو وقت الحصول على النتيجة. ولكن وقت التنفيذ أقل من Brute Force Attack وكذلك مساحة التخزين أقل من Lookup table. الفرق بين الـ 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 لها كما في الشكل التالي: نلاحظ في الشكل السابق إختلاف الناتج من عملية الـ 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 بل يفضل استخدام الدوال الجاهزة حسب لغة البرمجة كما في الشكل التالي: تطبيق: بلغة (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. قاعدة البيانات: تم إنشاء جدول للمستخدمين يحتوى على عدة حقول كما في الشكل أدناه. 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) 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) 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 ويكون شكل البيانات في الجدول كالتالي: الخلاصة: موضوع حماية بيانات المستخدمين متشعب كما نلاحظ في بعض النقاط السابقة. حاولت التركيز على الموضوع الرئيسي وهو استخدام الـ 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
  19. السلام عليكم ورحمة الله وبركاته طرحنا مسابقة عالم البرمجة للنقاش الهادف ، و هدفنا إثراء المحتوى العربي في مجال البرمجة ، فاز معنا الطرح المتميز و المفيد حيث قام بتقييم المقالات وترقيتها فريق عالم البرمجة سنذكر بهذه المقالة الفائزين معنا ، و مقالاتهم ، و نشكرهم على ماقدموا من فائدة للجميع ، و إثراء المحتوى العربي في البرمجة. الفائزون في شهر May-2017 المقال الفائز: المقال الفائز: المقال الفائز: المقال الفائز: المقال الفائز: المقال الفائز: المقال الفائز: المقال الفائز: في الختام: هدفنا في موقع عالم البرمجة إثراء المحتوى العربي في مجال البرمجة ، و تعزيز حب المساعدة بين المبرمجين تستطيع كتابة ماتحب في ساحات النقاش ، و يمكن تكون احد الفائزين معنا بمسابقة عالم البرمجة للنقاش الهادف فهي مازالت مستمره ايضا تستطيع مساعدة المبرمجين بالإجابة عن أسئلتهم ، و حل المشاكل التي تواجههم بقسم سؤال وجواب ؛ لتكون مرجع لبقية المبرمجين شعارنا في عالم البرمجة "إن في قضاء حوائج الناس لذة لا يَعرفها إلا من جربها، فافعل الخير مهما استصغرته فإنك لا تدري أي حسنة تدخلك الجنة." -ابن القيم- وممكن تستفيد من حبيبي القارئ اعلم ان فريق عالم البرمجة يصب كل جهودة لمساعدة المبرمجين الذين يخصصون من وقتهم لنشر العلم المفيد ويساعدون الغير ويارب يقدرنا نوقف معكم ونساعدكم قد مانقدر.
  20. السلام عليكم و رحمة الله و بركاته.. في هذا الموضوع سنتحدث عن عن علاقة إطار العمل ExpressJS مع الباترن NodeJS و كيفية عمل Pug as template engine اولا: حتى لا تكون هناك امور مبهمه و ابعاد الغيوم التي تدور حول اذهان الكثير من يريد العمل او التوجه لهذا الباترن يجب عرض بعض من الحقائق: الـNodeJS ليست لغة جديدة .. انما بيئة مبرمجة بالـJS. هذه البيئه تعمل في جهة السيرفر. ان كنت مبرمج AngularJS or ReactJS ليس هناك سطر محدد تربط فيه بين تطبيقك الNode مع الفريموورك الذي تعمل به. طريقة الربط تكون من خلال HTTP requests. تستطيع التعامل مع قواعد البيانات المختلفه مباشره بالـNode مثل MySQL, MariaDB و اشهرهم في هذا المجال هي MongoDB. نعود مرة اخرى للـExpressJS الذي هو يعتبر فريم وورك ابعد مما ان يقال عنه "مساعد" في تطبيقات الـNode. بإستخدام هذا الفريم وورك الذي يعمل كذلك في السيرفر، يتم تسهيل العمل و كمية الكود في تطبيق الـNode بشكل كبير جدا. مثال على تطبيق Node بإستخدام فريم وورك ExpressJS وسيتم شرح اجزائه جميعها var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('Hello World!') }); app.listen(3000, function () { console.log('Example app listening on port 3000!') }); اول سطرين استدعاء موديول الاكسبرس و من ثم دالة الاكسبرس في المتغير app الذي استخدمناه في صنع التطبيق. الشطر الثاني get هو عبارة عن نوع الـhttp request قد يكون GET, POST, DELETE, UPDATE etc …. بعد ذلك يأتي المسار - لتفاصيل اكثر حول هذا الجزء الرجاء زيارة الدرس السابق. نننقل الآن الى امر اخر في الـExpressJS وهو الـtemplating ( عمل الواجهات UI و استقبال البيانات المرسلة من الـNode ) لعمل الواجهات الديناميكيه يفضل استخدام محركات التمبليت التي تتوافق مع الاكسبرس وهي PUG - know as Jade Mustache EJS و في هذا الدرس سنستخدم المحرك الاول لعمل صفحة ويب اعتيادية و اخرى ديناميكية, لصنع واجهة بمحرك التمبليت pug يجب عليك ان تتقن او ان تاخذ فكره عن هذا المحرك لتعرف كيفية كتابه الكود ومثال على ذلك doctype html html(lang='en') head title Jade script(type='text/javascript'). foo = true; bar = function () {}; if (foo) { bar(1 + 5) } body h1 Jade - node template engine #container.col p You are amazing p | Jade is a terse and simple | templating language with a | strong focus on performance | and powerful features. يعادل في الHTML <!DOCTYPE html> <html lang="en"> <head> <title>Jade</title> <script type="text/javascript"> foo = true; bar = function () {}; if (foo) { bar(1 + 5) } </script> </head> <body> <h1>Jade - node template engine</h1> <div id="container" class="col"> <p>You are amazing</p> <p> Jade is a terse and simple templating language with a strong focus on performance and powerful features. </p> </div> </body> </html> لمعلومات اكثر حول اللغة اطلع على موقعهم الرسمي وبذلك سنقوم بإنشاء مجلدات ومستندات بهذه الطريقة حمل الـbootstrap وانقل كل من bootstrap.min.css إلى مجلد css , انقل bootstrap.min.js إلى مجلد js بعد ذلك إبدأ بتثبيت الـpackages التي سنعمل بها من خلال فتح الـterminal في مجلد myapp من خلال الاوامر التالية $ npm install express --save $ npm install cookie-parser --save $ npm install body-parser --save $ npm install mysql --save افتح ملف التطبيق app.js وقم بإستدعاء هذه البكجات var express = require('express'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var mysql = require('mysql'); الآن نبدأ بإعداد config فريم وورك الـExpressJS مع cookie-parser body-parser ( سنستخدمهم لاحقا ) var router = express.Router(); var app = express(); app.use(bodyParser.json({limit: "50mb"})); app.use(cookieParser()); var urlencodedParser = bodyParser.urlencoded({ extended: true, parameterLimit:50000 }); app.use("/", router); نضيف امكانية استخدام ملفات الـcss, js , images etc من خلال استخدام الـmiddleware ـexpress static بإعطاء مسار وهمي لهذه الملفات app.use('/css', express.static(__dirname + '/public/css')); app.use('/js', express.static(__dirname + '/public/js')); نرى وجود ملفات الـcss and js في مجلد public/css ولكن المسار الذي سنكتبه في الكود هو فقط css/ وليس بالضرورة ان يكون اسم المسار الوهمي ذو علاقه بالمسار الاساسي .. تستطيع ان تسميه مثلا sambosa/ و في الحقيقة هو public/css/ تحديد نوع الـtemplate engine الذي سنستخدمه في هذا التطبيق مع تحديد مسار مجلد الـviews الذي يحتوي على صفحات الموقع او التطبيق app.set('views', __dirname + '/views'); app.set('view engine', 'pug'); بعد إنشاء ملفات الـpug في إحدى الخطوات السابقه , انسخ والصق كود الصفحتين index.pug and layout.pug details.pug كلن على حدى //layout.pug doctype html html head title= title script(src='https://code.jquery.com/jquery-3.2.1.min.js') link(rel='stylesheet', href='/css/bootstrap.min.css') script(src='/js/bootstrap.min.js') body nav.navbar.navbar-default .container .navbar-header button.navbar-toggle.collapsed(type='button', data-toggle='collapse', data-target='#navbar', aria-expanded='false', aria-controls='navbar') span.sr-only Toggle navigation span.icon-bar span.icon-bar span.icon-bar a.navbar-brand(href='/') NodeJS #navbar.navbar-collapse.collapse ul.nav.navbar-nav li.active a(href='/') Home li a(href='/details') details li.dropdown a.dropdown-toggle(href='#', data-toggle='dropdown', role='button', aria-haspopup='true', aria-expanded='false') | Dropdown span.caret ul.dropdown-menu li a(href='#') Action li a(href='#') Another action li a(href='#') Something else here li.divider(role='separator') li.dropdown-header Nav header li a(href='#') Separated link li a(href='#') One more separated link block content //index.pug extends layout block content .container h1 #{msg} p #{username} , welcome to the first templating tutorial //details.pug extends layout block content .container .table-responsive h2 user table p This is all users we have so far table.table.table-bordered#example thead tr th Firstname th Lastname th Email tbody tr td John td Doe td john@example.com tr td Mary td Moe td mary@example.com tr td July td Dooley td july@example.com link(rel='stylesheet', type='text/css', href='https://cdn.datatables.net/v/dt/dt-1.10.15/datatables.min.css') script(type='text/javascript', src='https://cdn.datatables.net/v/dt/dt-1.10.15/datatables.min.js') script(). $(document).ready(function() { $('#example').DataTable(); }); ملاحظة: نستخدم extends layout لكي لا نكرر الـhead في الصفحات الاخرى , مثل include في الـPHP ( في هذا الدرس ) - الاستخدام الحقيقي للـlayout هو تصميم شكل معين لصفحات معينه .. كـtemplate خاص مثلا - معلومات اكثر من خلال الرابط هنا الأن تم تجهيز المستوى الأول من التطبيق وهو الـconfigurations , ننتقل إلى النص الآخر وهو الـroutes . اولا route الصفحة الرئيسية سيكون كالتالي app.get('/', function(req, res) { res.render('index', { title: 'Pug ExpressJS NodeJS Tutorial', username: 'abdulla', msg: 'Hey There!' }) }); نلاحظ ان مسار الـroute هو مجرد slash للامام .. بذلك يتم تحديد المسار الرئيسي للتطبيق, بعد ذلك res.render اي تعني response render حيث يقوم تطبيق الـNode بالأجابة من بعد عمل طلب request من طرف المستخدم , للتوضيح اكثر الـHTTP هو عباره عن بروتوكول طلب و اجابة وبذلك request and response - req, res فعند طلب العميل امر ما يجب عليه ان يحصل على شي ما في المقابل. و كما موضع في الدالة اعلاه ان الـresponse هو عباره عن عمل render لملف index.pug الموجود في مجلد views مع ارسال ثلاث متغيرات title, username and msg , و طريقة استقبال البيانات في الـpug هي كالتالي ( يوجد اكثر من طريقة ) h1 #{username} p Welcome to #{title} خطوة اخيره لتشغيل التطبيق يجب تحديد بورت معين ولنفترض 3000 app.listen(3000, function() { console.log('The app is running http://localhost:3000'); }); افتح الـterminal داخل مجلد التطبيق myapp واكتب الامر التالي لتشغيل التطبيق node app.js افتح المتطفح على المسار انتهى الدرس الاول .. تم من خلاله عمل configuration لتطبيق الـnode الذي سنستخدمه في الدروس القادمة تثبيت template engine و توصيله مع تطبيق الـNode ارسال بيانات من الـNode إلى pug الدرس القادم سيكون هناك سنعرض لكم كيفية التعامل مع قواعد البيانات وإرسال المعلومات الى pug للحصول على ملفات العمل تجدونهم في المرفقات, بعد تحميل الملف قم بفك الضغط انتقل إلى داخل المجلد واكتب الأمر التالي لتثبيت جميع الـpackages دفعة واحده $ npm install --save أضغط هنا لتحميل ملفات العمل anwbh-app.zip موقع مساعد لتحويل كودات الـHTML إلى Pug http://html2jade.org
  21. لماذا لاتستخدم مكتبات موجودة مثل gson or jakson فاستخدامك لها يختصر الكثير من الاكواد ويساعدك في تفادي الكثير من المشاكل.
  22. السلام عليكم و رحمة الله و بركاته. في هذه المقالة سنتعلم كيفية قراءة و استخراج بيانات الـJSON في الـAndroid Studio. ملاحظة: يفضل أن تكون قد قرأت مقالتي السابقة عن كيفية تحميل بيانات الـJSON في Android Studio من هنا. قبل أن نبدأ باستخراج البيانات أود أن نلقي نظرة على ماهية الـJSON المتعلق ب Flicker Api. باستخدام موقع jsonlint حيث يمكننا أن نرى التكوين الداخلي للـJSON بالاضافة الى التأكد من أن الملف هوة عبارة عن JSON. رابط ملف الـJSON هو: https://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsoncallback=1 كما نلاحظ أعلاه، الأشياء المحاطة بـ{} هي عبارة عن object و الأشياء المحاطة بـ[] هي عبارة عن array، لذلك نستنتج بأن الـJSON هو عبارة عن objects و arrays. في هذا الدرس سوف نقوم باستخراج الـtitle و الـtags من الـarray التي تدعى items. و الآن ننتقل الى التطبيق في الـ Android Studio... في البداية يجب علينا عمل التالي: ننشئ كلاس ونسميه photo و الذي يحتوي على متغيرات: title, tags و الذي بدوره سوف يخزن البيانات المطلوبة من الـJSON. إضافة getters and setters لهذه المتغيرات. package org.example.rami.flickerbrowser; import java.io.Serializable; /** * Created by Rami on 5/2/2017. */ class Photo implements Serializable{ private static final long serialVersionUID = 1L; private String mTitle; private String mTags; public Photo(String title, String tags) { mTitle = title; mTags = tags; } String getTitle() { return mTitle; } String getTags() { return mTags; } @Override public String toString() { return "Photo{" + "mTitle='" + mTitle + '\'' + ", mTags='" + mTags + '\'' + '}'; } } ثم ننشئ كلاس آخر و نسميه على سبيل المثال GetFlickerJsonData و الذي بدوره سوف يحتوي على الميثود onDownloadComplete التي تقوم بقراءة و استخراج المعلومات المطلوبة من الـJSON بالاضافة الى تخزينها في الاوبجكت من نوع Photo. ملاحظة: الميثود onDownloadComplete يتم استدعاؤها من الكلاس GetRawData الموجود في الدرس السابق الذي قام بتحميل ملف الـJSON و تمرير بياناته على شكل String للميثود. package org.example.rami.flickerbrowser; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; /** * Created by Rami on 5/3/2017. */ class GetFlickerJsonData implements GetRawData.OnDownloadComplete { private static final String TAG = "GetFlickerJsonData"; private List<Photo> mPhotoList = null; public GetFlickerJsonData() { Log.d(TAG, "GetFlickerJsonData called"); //here you do somthing additional } @Override public void onDownloadComplete(String data) { mPhotoList = new ArrayList<>(); try { JSONObject jsonData = new JSONObject(data); JSONArray itemsArray = jsonData.getJSONArray("items"); for(int i = 0; i < itemsArray.length(); i++){ JSONObject jsonPhoto = itemsArray.getJSONObject(i); String title = jsonPhoto.getString("title"); String tags = jsonPhoto.getString("tags"); Photo photoObject = new Photo(title, tags); mPhotoList.add(photoObject); Log.d(TAG, "onDownloadComplete: " + photoObject.toString()); } }catch (JSONException jsone){ jsone.printStackTrace(); Log.e(TAG, "onDownloadComplete: Error processing json data " + jsone.getMessage()); } } Log.d(TAG, "onDownloadComplete ends"); } لقد قمنا بعمل التالي: احاطة الكود بالكامل بtry و catch لمعالجة ظهور أي خطأ في ملف الـJSON. اضافة متغير mPhotoList من نوع List و الذي سوف يتم تخزين فيه الـphotos و التي تحتوي على بيانات الـJSON المستخرجة. اضافة Constructor لانشاء object من هذا الكلاس بحيث يتم اضافة أي شيء فيه على حسب تصميمك للبرنامج الكامل و طريقة عمله. اضافة الـmethod و هي onDownloadComplete و التي بدورها سوف تستخرج المعلومات المطلوبة من الـJSON المحمل كما هو موضح أعلاه. و هذه الـmethod تحتوي على: المتغير jsonData من نوع ـJSONObject و الذي يحتوي على كامل ملف الـJSON. المتغير itemsArray من نوع JSONArray والذي يحتوي على الـarray و التي تدعى items. و الآن نقوم باضافة loop و التي بدورها تقوم بالتالي: استخراج الـtitle و تخزينه بالمتغير title من نوع String. استخراج الـtags و تخزينه بالمتغير tags من نوع String. في النهاية نقوم بانشاء اوبجكت من نوع Photo و اضافته الى الـmPhotoList و التي تحتوي على جميع الـobjects من نوع Photo. لمزيد من المعلومات عن org.json package و الكلاسات الموجودة فيه: https://developer.android.com/reference/org/json/package-summary.html و هنا وصلنا الى نهاية هذا الدرس.😄 أتمنى أني قد وفقت بايصال المعلومة.
  23. بسم الله الرحمن الرحيم كيف تحمي بيانات المستخدمين من الإختراق عن طريق 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 خانات. كما نلاحظ تتمت الإزاحة 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. كما نلاحظ في الشكل السابق أن النص الناتج من عملية الـ Hashing نص ثابت الطول (10) خانات مهما أختلف طول النص في النص الأصلي في الجهة اليسار. أيضا نلاحظ أن 123 كانت عملية الـ Hashing لها واحدة (Fdeom83nyU) مهما تكررت يكون الناتج واحد. خوارزميات الـ Hashing: يوجد العديد من خوارزميات الـ Hashing من أشهرها: · MD5 Message Digest Algorithm 5 · SHA-1,SHA-2,SHA-3 Secure Hash Algorithm · BLAKE and BLAKE2 كيف يتم كسر كلمات المرور؟ في البداية يمكن إكتشاف كلمة المرور دون النظر إلى كيفية تخزينها في قاعدة البيانات هل هي بنص واضح أو مشفرة أو تم عمل 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 ( بالتأكيد لا يعرفها المخترق). ستتم هذه الطريقة حسب الملف الذي يمتلكه المخترق (الشكل بالأسفل) ستتم تجربة كلمات الملف كالتالي 2- Brute Force Attack هذه الطريقة شبيهه بالطريقة السابقة حيث أنها تعتمد أيضا على التخمين ولكن التخمين بالاعتماد على عمليات حسابية حيث يتم تجربة كل الاحتمالات الممكنة مثلا تجربة كل الاحتمالات لكلمة مرور ذات طول 6 خانات عبارة عن أرقام أو أحرف. فهنا الموضوع لا يعتمد على جدول أو ملف إنما تجربة أثناء وقت التنفيذ. وهذه الطريقة تأخذ وقت طويل يزيد وينقص حسب طول كلمة المرور. مثال : لنفرض أن المخترق لديه هذا الحساب User@mail.com وكلمة المرور هي 111333 ( بالتأكيد لا يعرفها المخترق). تعمل هذه الطريقة بهذا الشكل: سيتم تجربة كل الاحتمالات الممكنة حتى يصل إلى كلمة المرور. وقد تستمر إلى وقت طويل جداً حتى يتم إيجاد كلمة المرور. أيضا هنا تم استخدام (Online Attack) حيث أن المخترق يرسل للخادم في كل مرة اسم المستخدم مع احتمال لكلمة المرور بشكل متسلسل. ويمكن استخدام الـ (Offline Attack) إذا كانت كلمة المرور محفوظة كـ Hash حيث يكون لدى المخترق الـ Hashed Password فيمكن اكتشاف كلمة المرور بنفس الطريقة ولكن يتم أولا عمل Hash أُثناء التنفيذ لكل سلسلة من الحروف ثم مقارنتها مع الـ Hashed Password. لنفرض أن المخترق لديه هذه الكلمة (Hashed Password) التالية: 62b37844f7adeb0df6f180126327dca339fb7003 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 ) وليس أثناء وقت التنفيذ. فالعملية هي عملية بحث أو استعلام عادية للحصول على الـ Hash الموجودة في الجدول والمطابقة لما لدى المخترق (e9989db5dabeea617f40c8dbfd07f5fb). وهنا تكون كلمة المرور الأصلية هي Car. وطبعا هذه الطريقة تختصر الوقت لكن تعتمد كفائتها على كمية الكلمات الموجودة في الجدول. يوجد بعض البرامج التي تقوم بإنشاء هذه الجداول حسب بعض المعطيات لدى المخترق ( أي لا يشترط أن يكون هناك قائمة جاهزة للكلمات المشهورة إنما يمكن إنشائها) من هذه المعطيات أن يتم إنشاء جدول (Lookup tables) للكلمات ذات الطول من 1 إلى 7 خانات وتكون عبارة عن أرقام فقط. ويمكن أن تكون أرقام أو حروف أو رموز. كلما زادت المعطيات زاد وقت إنشاء هذه الجداول وزادت مساحة التخزين. 4- Rainbow Tables هو نوع مخصص من Lookup table بنفس الفكرة والاعتماد على (precomputation ). ولكن تعمل تحت مبدأ (space/time trade-off) وهذا يعني زيادة مساحة التخزين في سبيل تقليل وقت التنفيذ أو وقت الحصول على النتيجة. ولكن وقت التنفيذ أقل من Brute Force Attack وكذلك مساحة التخزين أقل من Lookup table. الفرق بين الـ 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 لها كما في الشكل التالي: نلاحظ في الشكل السابق إختلاف الناتج من عملية الـ 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 بل يفضل استخدام الدوال الجاهزة حسب لغة البرمجة كما في الشكل التالي: تطبيق: بلغة (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. قاعدة البيانات: تم إنشاء جدول للمستخدمين يحتوى على عدة حقول كما في الشكل أدناه. 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) 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) 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 ويكون شكل البيانات في الجدول كالتالي: الخلاصة: موضوع حماية بيانات المستخدمين متشعب كما نلاحظ في بعض النقاط السابقة. حاولت التركيز على الموضوع الرئيسي وهو استخدام الـ 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
  24. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته تعرفنا في بداية سلسلة مواضيعنا على طريقة من طرق تخزين البيانات وهي القوائم سواء الأحادية أو الثنائية، كما ونعلم بأن هناك طريقة أخرى احفظ مجموعة البيانات وهي أبسط طريقة والتي تتمثل في المصفوفات سواء المصفوفات العادية أو مصفوفات القوائم. في هذا الدرس سنتعرف على أحد أفضل الطرق في حفظ البيانات والتي تعتبر من الأفضل من ناحية العرض أو البحث وغيرها من الأمور المتعلقة بهذه المجموعة من البيانات. كما هو الحال في القوائم فإن الأشجار لديها أكثر من نوع وكل واحد منهم له خواصة واستخداماته. في هذا الدرس سنتعرف على أحد أنواع الأشجار وهو الأشجار الثنائية. ماذا نقصد بالأشجار الثنائية (Binary Trees): هو نوع من أنواع تمثيل البيانات ويتكون من مكونين أساسيين هما العقد (Nodes) و الأقصان (Arcs). عندما نقوم بتمثيل هذه الشجرة فإننا نقلب مفهومها فيكون الجذر (Root) في الأعلى والأقصان في الأسفل. سميت بالأشجار الثنائية لأنه كل عقد لديه عقيدين يرتبط فيهم وهما أبنائه ولديه والد واحد. هناك بعض المصطلحات التي يجب أن نتعرف عليها قبل الشروع الى صلب الموضوع: - العقد (Node): وهو كل عنصر موجود في الشجرة، وهو المكان الذي يتم حفظ البيانات داخله. أنواع خاصة من العقد (Nodes): - الجذر (Root): وهو العنصر الذي ليس لديه والد أي أنه أعلى عنصر في الشجرة. وكل شجرة تحتوي على جذر واحد فقط. - الأوراق (Leaves): وهذه هي مجموعة العقد التي ليس لديها أي أولاد. أي أنها في طرف الشجرة. - القصن (Arc): هو الرابط الذي يربط العقد بين بعضها وهو ذي اتجاه واحد من الأعلى الى الأسفل. يختلف مفهوم الشجرة الثنائية Binary Tree حيث أن بعضها لا تحقق بعض الشروط مما يؤدي بها الى أن تكون عبارة عن قائمة عادية لكن الأنواع الأخرى تحتوي على شروط التي تجعلها شجرة فعالة بأكبر قدر ممكن و تسمى بـ Binary Search Tree وهذا اساس جميع ما يلي. تمثيل الشجرة الثنائية (Binayr Tree): ١. عن طريق المصفوفات (ArrayLists): وهذه الطريقة لديها مشاكلها كما ذكراناها سابقاً. ٢. عن طريق القوائم الثنائية (Linked lists): وهذه هي الطريق الأمثل لتمثيل الشجر. أكواد الشجرة الثنائية (Binary Tree): أ- إنشاء كلاس خاص بالعقد (Nodes) : هذا الكلاس يسمى بـ BSTNode وهو كالتالي: public class BSTNode { protected int key; protected BSTNode right, left; public BSTNode(){ this(0); } public BSTNode(int k){ this(k, null, null); } public BSTNode(int k, BSTNode r, BSTNode l){ this.key = k; this.right = r; this.left = l; } public void addRight(BSTNode r){ this.right = r; } public void addLeft(BSTNode l){ this.left = l; } } هذا الكلاس يحتوي على المتغيرات التالية: - Key: وهو من نوع و int وهو المكان الذي يتم حفظ البيانات داخله. - right & left: وهما القصنان (Arcs) والذان يربطان العقد بأبنيه اليمين والشمال. كما نرى فإننا نستطيع إنشاء عنصر من هذا الكلاس سواء كان في بيانات أو لا. لديه أولاد أولا. كما وأنه لدينا ميثودين نستخدمها لإضافة ابن لليمين أو لليسار. ب- انشاء كلاس الشجرة الثنائية (Binary Search Tree): public class BST { private BSTNode root = null; public BST(){ } public void clear(){ this.root =null; } public boolean isEmpty(){ return root == null; } public void visit(BSTNode n){ System.out.println(n.key + " "); } } هنا نقوم بانشاء الهيكلة الأساسية للشجرة ويحتوي على التالي: - root: الجذر وهو رأس الشجرة والوحيد الذي يربطنا بكامل عناصر الشكرة. وفي بداية انشاء الشجرة يكون الجذر فاضي. لدينا الكثير من الميثود لكن سنذكر هنا الأشياء الأساسية ونترك الباقي للدرس القادم بحيث أنها تحتوي على الكثير من التفاصيل: * clear(): تستخدم لتفريغ الشجرة من البيانات؛ وبما أن الجذر هو الرابط الوحيد لعناصر الشجرة فإن حذف الجذر سيؤدي الى حذف الشجرة كلها. *isEmpty(): وستخدم للاستعمال اذا ما كانت الشجرة خالية أو لا. ترسل true في حال كانت الشجرة خالية. *visit(): تستخدم لطباعة قيمة القيمة داخل العقد المرسل. خلال الشرح القادم إن شاء الله سنتطرق لاضافة وحذف عناصر من الشجرة، كذلك كيف نقوم بطباعة جميع عناصر الشجرة. اسأل الله العلي العظيم بأني وفقت لاصال المعلومة بابسط طريقة في حفظ الله
  25. السلام عليكم و رحمة الله و بركاته. في هذه المقالة سنتعلم كيفية قراءة و استخراج بيانات الـJSON في الـAndroid Studio. ملاحظة: يفضل أن تكون قد قرأت مقالتي السابقة عن كيفية تحميل بيانات الـJSON في Android Studio من هنا. قبل أن نبدأ باستخراج البيانات أود أن نلقي نظرة على ماهية الـJSON المتعلق ب Flicker Api. باستخدام موقع jsonlint حيث يمكننا أن نرى التكوين الداخلي للـJSON بالاضافة الى التأكد من أن الملف هوة عبارة عن JSON. رابط ملف الـJSON هو: https://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsoncallback=1 كما نلاحظ أعلاه، الأشياء المحاطة بـ{} هي عبارة عن object و الأشياء المحاطة بـ[] هي عبارة عن array، لذلك نستنتج بأن الـJSON هو عبارة عن objects و arrays. في هذا الدرس سوف نقوم باستخراج الـtitle و الـtags من الـarray التي تدعى items. و الآن ننتقل الى التطبيق في الـ Android Studio... في البداية يجب علينا عمل التالي: ننشئ كلاس ونسميه photo و الذي يحتوي على متغيرات: title, tags و الذي بدوره سوف يخزن البيانات المطلوبة من الـJSON. إضافة getters and setters لهذه المتغيرات. package org.example.rami.flickerbrowser; import java.io.Serializable; /** * Created by Rami on 5/2/2017. */ class Photo implements Serializable{ private static final long serialVersionUID = 1L; private String mTitle; private String mTags; public Photo(String title, String tags) { mTitle = title; mTags = tags; } String getTitle() { return mTitle; } String getTags() { return mTags; } @Override public String toString() { return "Photo{" + "mTitle='" + mTitle + '\'' + ", mTags='" + mTags + '\'' + '}'; } } ثم ننشئ كلاس آخر و نسميه على سبيل المثال GetFlickerJsonData و الذي بدوره سوف يحتوي على الميثود onDownloadComplete التي تقوم بقراءة و استخراج المعلومات المطلوبة من الـJSON بالاضافة الى تخزينها في الاوبجكت من نوع Photo. ملاحظة: الميثود onDownloadComplete يتم استدعاؤها من الكلاس GetRawData الموجود في الدرس السابق الذي قام بتحميل ملف الـJSON و تمرير بياناته على شكل String للميثود. package org.example.rami.flickerbrowser; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; /** * Created by Rami on 5/3/2017. */ class GetFlickerJsonData implements GetRawData.OnDownloadComplete { private static final String TAG = "GetFlickerJsonData"; private List<Photo> mPhotoList = null; public GetFlickerJsonData() { Log.d(TAG, "GetFlickerJsonData called"); //here you do somthing additional } @Override public void onDownloadComplete(String data) { mPhotoList = new ArrayList<>(); try { JSONObject jsonData = new JSONObject(data); JSONArray itemsArray = jsonData.getJSONArray("items"); for(int i = 0; i < itemsArray.length(); i++){ JSONObject jsonPhoto = itemsArray.getJSONObject(i); String title = jsonPhoto.getString("title"); String tags = jsonPhoto.getString("tags"); Photo photoObject = new Photo(title, tags); mPhotoList.add(photoObject); Log.d(TAG, "onDownloadComplete: " + photoObject.toString()); } }catch (JSONException jsone){ jsone.printStackTrace(); Log.e(TAG, "onDownloadComplete: Error processing json data " + jsone.getMessage()); } } Log.d(TAG, "onDownloadComplete ends"); } لقد قمنا بعمل التالي: احاطة الكود بالكامل بtry و catch لمعالجة ظهور أي خطأ في ملف الـJSON. اضافة متغير mPhotoList من نوع List و الذي سوف يتم تخزين فيه الـphotos و التي تحتوي على بيانات الـJSON المستخرجة. اضافة Constructor لانشاء object من هذا الكلاس بحيث يتم اضافة أي شيء فيه على حسب تصميمك للبرنامج الكامل و طريقة عمله. اضافة الـmethod و هي onDownloadComplete و التي بدورها سوف تستخرج المعلومات المطلوبة من الـJSON المحمل كما هو موضح أعلاه. و هذه الـmethod تحتوي على: المتغير jsonData من نوع ـJSONObject و الذي يحتوي على كامل ملف الـJSON. المتغير itemsArray من نوع JSONArray والذي يحتوي على الـarray و التي تدعى items. و الآن نقوم باضافة loop و التي بدورها تقوم بالتالي: استخراج الـtitle و تخزينه بالمتغير title من نوع String. استخراج الـtags و تخزينه بالمتغير tags من نوع String. في النهاية نقوم بانشاء اوبجكت من نوع Photo و اضافته الى الـmPhotoList و التي تحتوي على جميع الـobjects من نوع Photo. لمزيد من المعلومات عن org.json package و الكلاسات الموجودة فيه: https://developer.android.com/reference/org/json/package-summary.html و هنا وصلنا الى نهاية هذا الدرس.😄 أتمنى أني قد وفقت بايصال المعلومة. تم ترقية هذا الطرح المميز الى صفحة المقالات
  26. بسم الله الرحمن الرحيم أطلقت google مؤخراً Android IoT وفي هذه المقالة البسيطة سنقوم بثبيت النسخة على ٌRaspberry Pi 3 وعمل مشروع بسيط. بداية عليك تحميل النسخة الخاصة بالـ Raspberry Pi 3 من هنا وبعد ذلك تثبيته على SD card حسب النظام الذي تستخدمه, بالنسبة لمستخدمي Ubuntu كالتالي sudo dd if=Downloads/androidthings_rpi3_devpreview_4.img of=/dev/yourFlash بعد ذلك ادخل SD card في Raspberry Pi وأوصل الكهرباء, وسلك Ethernet يفترض أن ترى مثل هذه الشاشة لاحظ وجود IP في أسفل الصفحة فهو الذي سنستخدمه في التواصل مع Raspberry Pi وإيصاله بالشبكة. الان نفتح Android studio وعن طريق terminal الموجود فيه نقوم بكتابة الأمر التالي adb connect <ip-address> ip address هو الرقم المكتوب لديك في أسفل الشاشة بعد Ethernet IP, لو تم الاتصال بشكل صحيح سيظهر هذا السطر connected to <ip-address>:5555 , بهذه الطريقة نكون متصلين بالـ Raspberry Pi لكن, نريد أن نجعل الأتصال عن طريق wifi فنقوم بكتابة الأمر التالي adb shell am startservice \ -n com.google.wifisetup/.WifiSetupService \ -a WifiSetupService.Connect \ -e ssid <Network_SSID> \ -e passphrase <Network_Passcode> ssid أسم الشبكة المراد الاتصال بها passcode الرقم السري الخاص بالشبكة انتهينا من فقرة التثبيت إن صح التعبير وقد يتخللها الكثير من الإشكالات وdebug وأرحب بأي استفسار لتجاوز هذه المهمة, الان لننتقل إلى برمجة الـ board. أولا علينا أن نقوم بتوصيل كما في الصورة وهنا شرح لـ pins الخاصة بـ Rpi الكود كالتالي public class MainActivity extends Activity { private Gpio mLedGpio; private boolean mLedState = false; Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button) findViewById(R.id.button); PeripheralManagerService service = new PeripheralManagerService(); //system service responsible for managing peripheral connections try { String pinName = "BCM4";//pin number mLedGpio = service.openGpio(pinName); mLedGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);//Output pin ,start with 0v } catch (IOException e) { e.printStackTrace(); } mButton.setOnClickListener(v -> { try { if (mLedState) { mLedGpio.setDirection(Gpio.ACTIVE_HIGH);//5v mLedState = false; }else { mLedGpio.setDirection(Gpio.ACTIVE_LOW);//0v mLedState = true; } } catch (IOException e) { e.printStackTrace(); } }); } } أما الـ UI فيحتوي button واحد id الخاص فيه هو button بداية لدينا PeripheralManagerService service = new PeripheralManagerService(); PeripheralManagerService هي Service من من نظام الأندرويد تسهل لك التعامل مع pin الخاصة بـ Rpi بعد ذلك قمنا بختيار pin عن طريق اسمه String pinName = "BCM4"; mLedGpio = service.openGpio(pinName); mLedGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW); وربطه بمتغير من نوع Gpio, ثم قمنا بضبط إعداده بأن يكون output ويبدأ بـ 0 فولت ويوجد العديد من Constant التي تساعد في هذه الاعدادت mButton.setOnClickListener(v -> { if (mLedState) { mLedGpio.setDirection(Gpio.ACTIVE_HIGH); mLedState = false; }else { mLedGpio.setDirection(Gpio.ACTIVE_LOW); mLedState = true; } هنا قلنا في حال تم الضغط على button تأكد من حالته, في حال كان on اجعله off والعكس, ونلاحظ أننا قمنا بأستخدام المزيد من Constant التي تحدثنا عنها في السطر السابق. ونلاحظ أيضا أننا كتبنا الدالة الخاصة بنا بطريقة lambda. لمعلومات أكثر عن أساسيات hardware واللاستزادة : https://developer.android.com/things/sdk/index.html
  1. عرض المزيد من النشاطات

عالم البرمجة

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