تقنيات نور التعليمية تقنيات نور التعليمية

آخر الأخبار

جاري التحميل ...

🔐 الدرس 17: إضافة نظام أذونات (Permissions) لحماية ملاحظات كل مستخدم

في هذا الدرس سنجعل التطبيق احترافيًا بحيث:

  • كل مستخدم يرى ملاحظاته فقط
  • لا يمكن لمستخدم فتح أو تعديل أو حذف ملاحظات شخص آخر
  • حماية الروابط URL بحيث لا يستطيع أحد العبث بها
  • تطبيق Authorization بعد أن أضفنا Authentication في الدرس السابق

هذا هو ما يجعل التطبيق جاهزًا ليصبح منتجًا حقيقيًا 💯✨

Permissions

🎯 ما ستتعلمه اليوم

  1. إضافة عمود user_id داخل notes لربط كل ملاحظة بصاحبها
  2. حماية مسارات (Routes) تعديل وحذف الملاحظات
  3. منع الوصول غير المصرح به Unauthorized Access
  4. تطبيق دالة حماية Decorator للتأكد من تسجيل الدخول
  5. جعل كل عمليات CRUD خاصة بالمستخدم الحالي فقط


🧱 الخطوة 1: ربط الملاحظات بالمستخدم — تعديل جدول Notes

لو لم يكن لدينا حقل user_id بالفعل في جدول الملاحظات… نضيفه هكذا:

migration (manual update)

استخدم SQLite Browser أو نفذ الأمر:

ALTER TABLE notes ADD COLUMN user_id TEXT;

🧱 الخطوة 2: عند إضافة ملاحظة جديدة نضع user_id

في add_note داخل app.py:

@app.route('/add', methods=['POST'])
def add_note():
    if 'user' not in session:
        flash("Please login first", "warning")
        return redirect(url_for('login'))

    title = request.form['title']
    content = request.form['content']
    user_email = session['user']['email']

    conn = sqlite3.connect('notes.db')
    c = conn.cursor()
    c.execute("INSERT INTO notes (title, content, user_id) VALUES (?, ?, ?)",
              (title, content, user_email))
    conn.commit()
    conn.close()

    flash("Note added successfully!", "success")
    return redirect(url_for('notes'))

🧱 الخطوة 3: عرض الملاحظات الخاصة بالمستخدم فقط

في صفحة notes:

@app.route('/notes')
def notes():
    if 'user' not in session:
        return redirect(url_for('login'))

    user_email = session['user']['email']

    conn = sqlite3.connect('notes.db')
    c = conn.cursor()
    c.execute("SELECT id, title, content FROM notes WHERE user_id = ?", (user_email,))
    notes_list = c.fetchall()
    conn.close()

    return render_template('notes.html', notes=notes_list)

🧱 الخطوة 4: حماية تعديل الملاحظات (Edit)

قبل تعديل الملاحظة يجب التأكد:

✔ أن الملاحظة تخص المستخدم
❌ وإلا → نطرده برسالة Access Denied

@app.route('/edit/<int:note_id>', methods=['GET', 'POST'])
def edit_note(note_id):
    if 'user' not in session:
        return redirect(url_for('login'))

    user_email = session['user']['email']

    conn = sqlite3.connect('notes.db')
    c = conn.cursor()

    # التأكد أن الملاحظة ملك للمستخدم
    c.execute("SELECT title, content, user_id FROM notes WHERE id = ?", (note_id,))
    note = c.fetchone()

    if not note or note[2] != user_email:
        flash("Access Denied!", "danger")
        return redirect(url_for('notes'))

    if request.method == 'POST':
        new_title = request.form['title']
        new_content = request.form['content']

        c.execute("UPDATE notes SET title=?, content=? WHERE id=?",
                  (new_title, new_content, note_id))
        conn.commit()
        conn.close()

        flash("Note updated successfully!", "success")
        return redirect(url_for('notes'))

    conn.close()
    return render_template('edit.html', note_id=note_id, title=note[0], content=note[1])

🧱 الخطوة 5: حماية حذف الملاحظات (Delete)

@app.route('/delete/<int:note_id>')
def delete_note(note_id):
    if 'user' not in session:
        return redirect(url_for('login'))

    user_email = session['user']['email']

    conn = sqlite3.connect('notes.db')
    c = conn.cursor()

    # التأكد من ملكية الملاحظة
    c.execute("SELECT user_id FROM notes WHERE id = ?", (note_id,))
    owner = c.fetchone()

    if not owner or owner[0] != user_email:
        flash("Access Denied!", "danger")
        return redirect(url_for('notes'))

    c.execute("DELETE FROM notes WHERE id = ?", (note_id,))
    conn.commit()
    conn.close()

    flash("Note deleted successfully!", "success")
    return redirect(url_for('notes'))

🧱 الخطوة 6: إضافة Decorator بسيط لحماية الصفحات

بدل ما نكرر كود "لو المستخدم مش مسجل دخول":

في أعلى app.py:

from functools import wraps

def login_required(f):
    @wraps(f)
    def protected(*args, **kwargs):
        if 'user' not in session:
            flash("Please login first", "warning")
            return redirect(url_for('login'))
        return f(*args, **kwargs)
    return protected

ثم نستخدمه هكذا:

@app.route('/notes')
@login_required
def notes():
    ...

🎯 ما الذي حققناه في هذا الدرس؟

✔ كل مستخدم يرى ملاحظاته فقط
✔ لا يمكن فتح / تعديل / حذف ملاحظات مستخدم آخر
✔ حماية روابط URL
✔ حماية الصفحات باستخدام Decorator
✔ التطبيق أصبح آمن واحترافي ومناسب للسيرفرات


🧪 تمرين الدرس 17: حماية ملاحظات كل مستخدم بنظام صلاحيات كامل

🔸 المطلوب

قم بتطوير النظام بحيث:

1- يظهر زر تعديل وحذف الملاحظة فقط لصاحب الملاحظة الحقيقي
(أي مستخدم آخر يدخل على صفحة notes لن يرى هذه الأزرار)

2- إذا حاول شخص الدخول إلى رابط مباشر مثل:

/edit/5

وهو لا يملك الملاحظة 5 → يجب أن تظهر له رسالة:

Access Denied

3- عدّل صفحة عرض الملاحظات بحيث يظهر أسفل كل ملاحظة:

تمت الإضافة بواسطة: البريد الإلكتروني للمستخدم

ولكن لا تسمح بالتعديل إلا لصاحب الملاحظة.

هدف التمرين:
فهم كيف يعمل Authorization في مشروع Flask Notes App
(وهذا فرق مهم عن Authentication)


الحل الكامل للتمرين

🔹 الخطوة 1: تعديل صفحة notes.html لإخفاء الأزرار لغير المالك

افتح templates/notes.html
وأضف داخل كل بطاقة ملاحظة الشرط التالي:

<div class="card mb-3">
    <div class="card-body">
        <h5>{{ note[1] }}</h5>
        <p>{{ note[2] }}</p>

        <p class="text-muted" style="font-size: 14px;">
            تمت الإضافة بواسطة: {{ note[3] }}
        </p>

        {% if session['user']['email'] == note[3] %}
        <a href="/edit/{{ note[0] }}" class="btn btn-warning btn-sm">تعديل</a>
        <a href="/delete/{{ note[0] }}" class="btn btn-danger btn-sm">حذف</a>
        {% endif %}
    </div>
</div>

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


🔹 الخطوة 2: تعديل Route جلب الملاحظات لإرجاع user_id

في app.py داخل route صفحة notes:

@app.route('/notes')
@login_required
def notes():
    user_email = session['user']['email']

    conn = sqlite3.connect('notes.db')
    c = conn.cursor()

    c.execute("""
        SELECT id, title, content, user_id 
        FROM notes 
        WHERE user_id = ?
    """, (user_email,))

    notes_list = c.fetchall()
    conn.close()

    return render_template('notes.html', notes=notes_list)

الآن نرسل الـ user_id إلى الصفحة لعرض اسم منشئ الملاحظة.


🔹 الخطوة 3: حماية صفحة تعديل الملاحظات

في Route التعديل:

@app.route('/edit/<int:note_id>', methods=['GET', 'POST'])
@login_required
def edit_note(note_id):
    user_email = session['user']['email']

    conn = sqlite3.connect('notes.db')
    c = conn.cursor()

    c.execute("SELECT id, title, content, user_id FROM notes WHERE id = ?", (note_id,))
    note = c.fetchone()

    if not note:
        flash("Note not found!", "danger")
        return redirect(url_for('notes'))

    # حماية ملكية الملاحظة
    if note[3] != user_email:
        flash("Access Denied!", "danger")
        return redirect(url_for('notes'))

    if request.method == 'POST':
        new_title = request.form['title']
        new_content = request.form['content']

        c.execute("UPDATE notes SET title=?, content=? WHERE id=?", 
                  (new_title, new_content, note_id))
        conn.commit()
        conn.close()

        flash("Note updated successfully!", "success")
        return redirect(url_for('notes'))

    conn.close()
    return render_template('edit.html', note=note)

🔹 الخطوة 4: حماية صفحة حذف الملاحظات

@app.route('/delete/<int:note_id>')
@login_required
def delete_note(note_id):
    user_email = session['user']['email']

    conn = sqlite3.connect('notes.db')
    c = conn.cursor()

    c.execute("SELECT user_id FROM notes WHERE id = ?", (note_id,))
    owner = c.fetchone()

    if not owner or owner[0] != user_email:
        flash("Access Denied!", "danger")
        return redirect(url_for('notes'))

    c.execute("DELETE FROM notes WHERE id = ?", (note_id,))
    conn.commit()
    conn.close()

    flash("Note deleted successfully!", "success")
    return redirect(url_for('notes'))

🔥 هكذا نكون طبقنا نظام أذونات كامل:

✔ ملاحظات كل مستخدم تخصه فقط
✔ ممنوع تعديل أو حذف ملاحظات غيرك
✔ URL Protected حتى لو كتب شخص رابط ملاحظة ليست له
✔ إظهار أزرار التحكم لصاحب الملاحظة فقط
✔ تعزيز مستوى الأمان والخصوصية في التطبيق

عن الكاتب

Tamer Ahmed

التعليقات


اتصل بنا

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

جميع الحقوق محفوظة

تقنيات نور التعليمية