نحوه ایجاد دسترسی برای فایل های Static در Flask

پیش از همه چیز مرسی که کلیک کردید تا این نوشته کوتاه رو مطالعه کنید D:خب امروز میخوام راجب یه مبحث نسبتا امنیتی و مهم این نوشته رو منتشر کنم و اونم ایجاد دسترسی برای فایل های Static پروژه هامون هست حالا شاید این سوال به ذهنتون بیاد این یعنی چی؟خب گاهی ما یه سری فایل JS دارم مثلا توی Admin Panel پروژه امون و داره یه سری درخواست fetch میزنه به سرور و پاسخ رو میگیره ونمایش میده خب وقتی ما محدودیتی نزاریم روی فایل های Static که مثلا این فایل رو فقط به ادمین ها نشون بده (هر کاربری که بتونه با پسورد و یوزنیم به پنل ادمین وارد بشه )هر کاربری میتونه با رفتن به آدرس www.example.com/admin/static/Js/request.jsو میتونه فایل request.js رو ببینیه خب شاید بگید بببینه خب یعنی چی؟خب اینجوری ما داریم نحوه کار سیستممون و عملکردش رو به همه نشون میدیم و عملا یه ضعف به حساب می یاد و خوب نیست اینجوری باشه برای این کار باید خودمون یه سری محدودیت تنظیم کنیم و خودمون بیاییم و اون فایل هارو سرو کنیم برای ادمین پنل روش های دیگه هم هست مثلا بیاییم توی Nginx یه سری تنظیم ست کنیم که اگر کاربر فلان هدر رو داشت بیا این فایل رو براش سرو کنولی باز این یه ضعف داره که کاربر ها با آزمون و خطا (brute force) و ست کردن هدر میتونن وارد سیستم بشن بلاخره و زیاد امن نیست خب حالا باید چجوری عمل کنیم ؟برای این کار میتونیم سرو کردن یه سری فایل خاص که میخواییم رو خودمون به وسیله Flask انجام بدیمبرای این کار اول باید یه view بسازیم که مسئول سرو کردن فایل های خاص برای یه دسته کاربر خاص باشه خب قبل از ساخت view باید با چند تا چیز اشنا بشیم# send_from_directory from flask import send_from_directoryاین تابع میاد و یه فایل رو با مسیرش و میگیره و برای ما ارسال میکنه سمت کاربردر اصل خود این تابع میاد و از تابع send_file استفاده میکنه برای ارسال فایل# function input args def send_from_directory( directory: t.Union[os.PathLike, str], path: t.Union[os.PathLike, str], **kwargs: t.Any, ) -> Response: Send a file from within a directory using :func:`send_file`.مورد بعدی دونستن Decorator ها هست عموما توی ریمورک های تحت وب ما زیاد از Decorator ها استفاده می کنیم توی Flask, Django, Fastapi و به صورت بسیار زیاد مورد استفاده قرار میگیره ...و یه توضیح خلاصه اش میشه Decorator ها یه سری تابع هستن که میاد قبل اجرای یه تابع خاص اجرا میشن و با @ هم میان قرار می گیرن سینتکس دکوریتور ها :@simple_decorator def _pi(): return 3.14برای یادگیری بیشتر راجب دکوریتور ها توی پایتون:https://www.geeksforgeeks.org/decorators-in-python/خب برای شروع اول باید یه دکوریتور برای مثلا ادمین ها بسازیم که فقط کاربرانی که به عنوان ادمین لاگین کردن بتونن دسترسی داشته باشنیه چیزی مثل:@admin_login_required (شما میتونید از اکستنشن هایی مثل Flask-login هم برای لاگین کردن کاربر ها و استفاده از دکوریتور هاش استفاده کنید)def login_required(f): @wraps(f) def inner(*args, **kwargs): if not session.get(admin-id): return redirect(url_for(auth.login_view)) return f(*args, **kwargs) return innerتوی مثال بالا یه دکتوریتور خیلی ساده نوشتیم که میاد چک میکنه کاربرانی که مقدار admin-id توی session خودشون دارن رو دسترسی بده در غیر ای صورت ریدایرکت کنه به صفحه لاگین توی بلوپرینت Authخب حالا باید مسیر فایل هایی که میخواییم سرو کنم رو مشخص کنیم توجه: فایل هارو توی مسیر Static نزارید چون به طور دیفالت خود Flask میاد محتویات Static رو سرو می کنه پیشنهاد میکنم یه دایرکتوری دیگه کنار static ایجاد کنید و داخل اون کار کنید مثلا برای admin یه دایرکتوری static داشته باشید که همه دسترسی دارن و یه دایرکتوری admin_static_private داشته باشید که فقط برای کاربرانی هست که به عنوان ادمین لاگین کردنو مسیر این فولدر رو میتونید توی کانفیگ های خود متغییر اصلی app ذخیره کنید یا توی فایل کانفیگ هاتون مثل جنگو توی یه متغییر ذخیره کنید توی این مثال برای سادگی من توی کانفیگ های اپ گزاشتمشنحوه ست کردن :app.config[admin_static_private] = pathlib.Path(location of directory)خب بریم سراغ View@app.route(/admin_static/<path:path>/) @admin_login_required def admin_static(path # remember this): ``` later when we wanna serve some static file we should call this function syntax: blueprint.functionName ``` # check path exists if os.path.exists(os.path.join(app.config[admin_static_private], path) ): return send_from_directory(app.config[admin_static_private], path) else: # if path not found return `File Not Found! <>`, 404خب کد بالا چیز زیادی اتفاق نیافتاده اول اومدیم یه View تعریف کردیم با مسیر /admin_static/ شروع میشه و بعدش یه پارامتر از نوع path تعریف کردم ما میتونیم نوع پارامتر های ورودی رو هم توی URL مشخص کنیم برای مطالعه بیشتر https://flask.palletsprojects.com/en/2.2.x/api/#url-route-registrationو بعد اول اومدم چک کردم که اون فایل درخواستی وجود داره یا نه با استفاده از متد exists توی ماژول os و بعدش اگر وجود داشت اونو با استفاده از send_from_directory سرو میکنیم برای ادمین در صورتی که وجود نداشته باشه خطای 404 (Not Found) بر میگردونه خب توی template هامون چجوری باید مسیر دهی کنیم ؟برای این کار میتونیم از تابع url_for استفاده کنیم برای مسیر دهی به آدرس هامونمثال:<script src = {{ url_for(admin.admin_static, path=JS/ticket.js) }} > # path parameter should be same as the parameter that we define in view route address and function input فقط باید توجه داشته باشید هر اسمی که برای پارامتر ورودی تابعی که قراره این فایل هارو سرو کنه رو باید اینجا هم رعایت کنید ما اونجا اسمش رو path گزاشته بودیم پس باید اینجا هم path بزاریم و حالا خروجی:مثال فایلی که وجود نداره و خطای 404 میده سرورمثال فایلی که وجود داره و سرور اونو برای کاربر برگردونده با کد 200و با دکوریتور admin_login_required هم از این مسیر محافظت کردیم و تنها کسایی که به عنوان ادمین وارد سیستم شده باشن میتونن درخواست بدن برای فایل ها و دریافتشون کننبرای مطالعه بیشتر راجب این موضوع:https://stackoverflow.com/questions/27611671/restrict-static-file-access-to-logged-in-usersمرسی که تا اینجا وقت گزاشتید و مطالعه کردید :)