FLASK-PRINCIPAL(1) Flask Principal FLASK-PRINCIPAL(1)

flask-principal - مستندات Flask-Principal

"من آنم که هستم"

پکیج Flask-Principal چارچوبی با پیوند بسیار منعطف و سست (loose) را برای پیوند دادن ارائه‌دهندگان دو نوع سرویس فراهم می‌کند که معمولاً در بخش‌های متفاوتی از یک برنامه وب قرار دارند:

1.
ارائه‌دهندگان احراز هویت (Authentication providers)
2.
ارائه‌دهندگان اطلاعات کاربر (User information providers)

به عنوان مثال، یک ارائه‌دهنده احراز هویت می‌تواند OAuth با استفاده از Flask-OAuth باشد و اطلاعات کاربر نیز ممکن است در یک پایگاه داده رابطه‌ای ذخیره شود. انعطاف‌پذیری و ارتباط سست این چارچوب با استفاده از سیگنال‌ها به عنوان رابط میسر شده است.

مؤلفه‌های اصلی عبارتند از Identity، Needs، Permission و IdentityContext.

1.
مؤلفه Identity نمایانگر کاربر است و برای هر درخواست از مکان‌های گوناگون (مانند نشست یا session) بارگذاری یا ذخیره می‌شود. Identity در واقع آواتار کاربر در سیستم است که حقوق دسترسی کاربر را در بر دارد.
2.
مؤلفه Need کوچک‌ترین واحد در کنترل دسترسی است و پارامتری خاص را برای شرایط موجود بازنمایی می‌کند؛ برای نمونه: «دارای نقش مدیر است» ("has the admin role")، «می‌تواند پست‌های وبلاگ را ویرایش کند» ("can edit blog posts").

هر Need می‌تواند هر تاپل (tuple) یا شیء دلخواهی باشد، اما یک تاپل به بهترین شکل مناسب این کار است. انواع از پیش طراحی‌شدهٔ Need (جهت صرفه‌جویی در کدنویسی) به صورت جفت‌های (method, value) هستند که در آن‌ها method برای تعیین موارد متداول نظیر "role"، "user" و غیره استفاده می‌شود و value نیز مقدار آن است. نمونه‌ای از این ساختار ('role', 'admin') است که نشان‌دهنده Need مربوط به نقش admin خواهد بود؛ یا سه‌تایی‌ها (Triples) برای سناریوهایی نظیر «مجوز ویرایش یک نمونهٔ خاص از یک شیء یا سطر»، که ممکن است به شکل سه‌تایی ('article', 'edit', 46) بازنمایی شود؛ که در آن 46 کلید/شناسهٔ (ID) آن سطر/شیء است.

در اصل، ماهیت و چیستی Needs تا حد زیادی بر عهده کاربر است و ساختار آن منعطف طراحی شده تا بتوان با استفاده از نمونه‌های سفارشی به عنوان Need، به هر نیازی پاسخ داد.

در حالی که Need مجوزی برای دسترسی به یک منبع است، یک Identity باید مجموعه‌ای از نیازها (Needs) را که به آن‌ها دسترسی دارد ارائه دهد.

3.
مؤلفه Permission مجموعه‌ای از نیازمندی‌ها (requirements) است که برای دسترسی به یک منبع، وجود دست‌کم یکی از آن‌ها الزامی است.
4.
مؤلفه IdentityContext بستر (context) یک هویت (Identity) خاص در برابر یک Permission معین است. این مؤلفه می‌تواند هم به عنوان context manager و هم به عنوان دکوراتور (decorator) استفاده شود.
[نمودار]

برای کاربران Flask-Principal (نه ارائه‌دهندگان احراز هویت)، تعریف محدودیت دسترسی هم به عنوان دکوراتور (decorator) و هم به عنوان مدیر زمینه (context manager) بسیار آسان است. در ادامه یک مثال شروع سریع ساده به همراه توضیحات درون‌کد ارائه شده است:

from flask import Flask, Response
from flask.ext.principal import Principal, Permission, RoleNeed
app = Flask(__name__)
# load the extension
principals = Principal(app)
# Create a permission with a single Need, in this case a RoleNeed.
admin_permission = Permission(RoleNeed('admin'))
# protect a view with a principal for that need
@app.route('/admin')
@admin_permission.require()
def do_admin_index():
    return Response('Only if you are an admin')
# this time protect with a context manager
@app.route('/articles')
def do_articles():
    with admin_permission.require():
        return Response('Only if you are admin')

ارائه‌دهندگان احراز هویت باید از سیگنال identity-changed برای مشخص کردن اینکه یک درخواست احراز هویت شده است استفاده کنند. برای نمونه، کد زیر مثالی فرضی از نحوه ترکیب افزونه محبوب Flask-Login http://packages.python.org/Flask-Login با Flask-Principal است:

from flask import Flask, current_app, request, session
from flask.ext.login import LoginManager, login_user, logout_user, \
     login_required, current_user
from flask.ext.wtf import Form, TextField, PasswordField, Required, Email
from flask.ext.principal import Principal, Identity, AnonymousIdentity, \
     identity_changed
app = Flask(__name__)
Principal(app)
login_manager = LoginManager(app)
@login_manager.user_loader
def load_user(userid):
    # Return an instance of the User model
    return datastore.find_user(id=userid)
class LoginForm(Form):
    email = TextField()
    password = PasswordField()
@app.route('/login', methods=['GET', 'POST'])
def login():
    # A hypothetical login form that uses Flask-WTF
    form = LoginForm()
    # Validate form input
    if form.validate_on_submit():
        # Retrieve the user from the hypothetical datastore
        user = datastore.find_user(email=form.email.data)
        # Compare passwords (use password hashing production)
        if form.password.data == user.password:
            # Keep the user info in the session using Flask-Login
            login_user(user)
            # Tell Flask-Principal the identity changed
            identity_changed.send(current_app._get_current_object(),
                                  identity=Identity(user.id))
            return redirect(request.args.get('next') or '/')
    return render_template('login.html', form=form)
@app.route('/logout')
@login_required
def logout():
    # Remove the user information from the session
    logout_user()
    # Remove session keys set by Flask-Principal
    for key in ('identity.name', 'identity.auth_type'):
        session.pop(key, None)
    # Tell Flask-Principal the user is anonymous
    identity_changed.send(current_app._get_current_object(),
                          identity=AnonymousIdentity())
    return redirect(request.args.get('next') or '/')

ارائه‌دهندگان اطلاعات کاربر باید به سیگنال identity-loaded متصل شوند تا هرگونه اطلاعات اضافی مانند نقش‌ها (roles) را به نمونه Identity اضافه کنند. در ادامه یک مثال فرضی دیگر با استفاده از Flask-Login آورده شده است که می‌تواند با مثال پیشین ترکیب شود. این مثال نشان می‌دهد که چگونه می‌توان از یک ساختار مجوز مبتنی بر نقش استفاده کرد:

from flask.ext.login import current_user
from flask.ext.principal import identity_loaded, RoleNeed, UserNeed
@identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
    # Set the identity user object
    identity.user = current_user
    # Add the UserNeed to the identity
    if hasattr(current_user, 'id'):
        identity.provides.add(UserNeed(current_user.id))
    # Assuming the User model has a list of roles, update the
    # identity with the roles that the user provides
    if hasattr(current_user, 'roles'):
        for role in current_user.roles:
            identity.provides.add(RoleNeed(role.name))

حال برای نمونه فرض کنید تنها می‌خواهید نویسنده یک پست وبلاگ قادر به ویرایش مقاله مذکور باشد. این هدف با ساخت اشیاء مورد نیاز Need و Permission، و افزودن منطق بیشتر به پردازشگر سیگنال identity_loaded قابل دستیابی است. برای مثال:

from collections import namedtuple
from functools import partial
from flask.ext.login import current_user
from flask.ext.principal import identity_loaded, Permission, RoleNeed, \
     UserNeed
BlogPostNeed = namedtuple('blog_post', ['method', 'value'])
EditBlogPostNeed = partial(BlogPostNeed, 'edit')
class EditBlogPostPermission(Permission):
    def __init__(self, post_id):
        need = EditBlogPostNeed(unicode(post_id))
        super(EditBlogPostPermission, self).__init__(need)
@identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
    # Set the identity user object
    identity.user = current_user
    # Add the UserNeed to the identity
    if hasattr(current_user, 'id'):
        identity.provides.add(UserNeed(current_user.id))
    # Assuming the User model has a list of roles, update the
    # identity with the roles that the user provides
    if hasattr(current_user, 'roles'):
        for role in current_user.roles:
            identity.provides.add(RoleNeed(role.name))
    # Assuming the User model has a list of posts the user
    # has authored, add the needs to the identity
    if hasattr(current_user, 'posts'):
        for post in current_user.posts:
            identity.provides.add(EditBlogPostNeed(unicode(post.id)))

مرحله بعدی محافظت از نقطه پایانی (endpoint) است که به کاربر اجازه ویرایش یک مقاله را می‌دهد. این کار با ایجاد یک شیء دسترسی به‌صورت پویا در حین اجرا با استفاده از شناسه (ID) منبع (در این مورد، پست وبلاگ) انجام می‌شود:

@app.route('/posts/<post_id>', methods=['PUT', 'PATCH'])
def edit_post(post_id):
    permission = EditBlogPostPermission(post_id)
    if permission.can():
        # Save the edits ...
        return render_template('edit_post.html')
    abort(403)  # HTTP Forbidden
افزونهٔ Principal.
پارامترها (Parameters)
  • app -- برنامهٔ flask برای توسعه و اعمال افزونه.
  • use_sessions -- مشخص می‌کند که آیا از نشست‌ها (sessions) برای استخراج و ذخیرهٔ هویت استفاده شود یا خیر.
  • skip_static -- مشخص می‌کند که آیا نقاط پایانی (endpoints) ایستا نادیده گرفته شوند یا خیر.
دکوراتور برای تعریف یک تابع به‌عنوان بارگذار هویت (identity loader).

تابع بارگذار هویت پیش از پردازش درخواست فراخوانی می‌شود تا هرگونه هویت ارائه‌شده را پیدا کند. نخستین هویت یافت‌شده برای بارگذاری استفاده خواهد شد.

به‌عنوان مثال:

app = Flask(__name__)
principals = Principal(app)
@principals.identity_loader
def load_identity_from_weird_usecase():
    return Identity('ali')
دکوراتور برای تعریف یک تابع به‌عنوان ذخیره‌کنندهٔ هویت (identity saver).

تابع ذخیره‌کنندهٔ هویت هنگامی که هویت تنظیم می‌شود فراخوانی خواهد شد تا آن را برای درخواست بعدی پایدار (ذخیره) کند.

به‌عنوان مثال:

app = Flask(__name__)
principals = Principal(app)
@principals.identity_saver
def save_identity_to_weird_usecase(identity):
    my_special_cookie['identity'] = identity
تنظیم هویت جاری.
پارامترها (Parameters)
identity -- هویتی که باید تنظیم شود.

نمایانگر نیازمندی‌ها (needs) است که وجود هر یک از آن‌ها برای دسترسی به یک منبع الزامی است.
پارامترها (Parameters)
needs -- نیازمندی‌های این مجوز.
مشخص می‌کند که آیا هویت موردنظر به این مجوز دسترسی دارد یا خیر.
پارامترها (Parameters)
identity -- هویت موردنظر.
مشخص می‌کند که آیا بافتار (context) موردنیاز برای این مجوز دسترسی دارد یا خیر.

این متد یک بافتار هویت ایجاد می‌کند و دسترسی داشتن آن به این مجوز را می‌سنجد.

مجوز جدیدی شامل نیازمندی‌هایی که در این مجوز وجود دارند و در دیگری نیستند ایجاد می‌کند (تفاضل).
مشخص می‌کند که آیا نیازمندی‌های این مجوز زیرمجموعه‌ای از مجوز دیگر است یا خیر.
پارامترها (Parameters)
other -- مجوز دیگر.
یک principal برای این مجوز ایجاد می‌کند.

این principal می‌تواند به‌عنوان یک مدیر بافتار (context manager) یا یک دکوراتور استفاده شود.

در صورت ارسال http_exception، تابع abort() با کد استثنای HTTP فراخوانی خواهد شد. در غیر این صورت، چنانچه هویت نیازمندی‌ها را برآورده نکند، یک استثنای PermissionDenied صادر خواهد شد.

پارامترها (Parameters)
http_exception -- کد استثنای HTTP (مانند 403، 401 و غیره).
معکوس وضعیت جاری را بازمی‌گرداند (needs->excludes، excludes->needs).
بررسی می‌کند که آیا مجوز در دسترس است یا خیر و در صورت عدم دسترسی، استثنای مربوطه را صادر می‌کند. این روش زمانی کاربرد دارد که تنها بخواهید بدون قرار دادن همه‌چیز در یک بلوک ()require، مجوز را بررسی کنید.

این معادل است با:

with permission.require():
    pass
مجوز جدیدی با اجتماع نیازمندی‌های این مجوز و دیگری ایجاد می‌کند.
پارامترها (Parameters)
other -- مجوز دیگر.
نمایانگر هویت کاربر است.
پارامترها (Parameters)
  • id -- شناسهٔ کاربر (user id).
  • auth_type -- نوع احراز هویت استفاده‌شده برای تأیید هویت کاربر.

هویت برای نشان دادن هویت کاربر در سامانه به کار می‌رود. این شیء در هنگام ورود به سیستم (login)، یا در ابتدای درخواست بر اساس داده‌های بارگذاری‌شده از نشست (session) کاربر ایجاد می‌شود.

پس از بارگذاری، این شیء با استفاده از سیگنال identity-loaded ارسال می‌گردد و باید با اطلاعات موردنیاز تکمیلی مقداردهی شود.

نیازمندی‌هایی (Needs) که توسط این هویت برآورده می‌شوند، باید پس از بارگذاری به مجموعهٔ provides اضافه شوند.

مشخص می‌کند که آیا هویت به این مجوز دسترسی دارد یا خیر.
پارامترها (Parameters) permission -- مجوزی که فراهم بودن آن بررسی می‌شود.
یک هویت ناشناس
زمینهٔ (context) یک هویت برای یک مجوز.

نکته:

در سناریوهای معمول، principal معمولاً با فراخوانی متد flaskext.Permission.require ایجاد می‌شود.

این principal هم به عنوان یک مدیر زمینه (context manager) و هم به عنوان یک دکوراتور (decorator) عمل می‌کند. فراهم بودن مجوز در هویت بررسی می‌شود، و در صورت موجود بودن، روند ادامه می‌یابد (context manager) یا تابع اجرا می‌شود (decorator).

این‌که آیا هویت به این مجوز دسترسی دارد یا خیر
مجوز مربوط به این principal
هویت مربوط به این principal

یک نیاز (need) الزامی

این صرفاً یک تاپل نام‌دار (named tuple) است و عملاً هر تاپلی کار خواهد کرد.

از ویژگی method می‌توان برای جستجوی عنصر ۰ و از ویژگی value برای جستجوی عنصر ۱ استفاده کرد.

نام مستعار (alias) برای functools.partial(<class 'flask_principal.Need'>, 'role')
نام مستعار (alias) برای functools.partial(<class 'flask_principal.Need'>, 'id')
یک نیاز به آیتم (item need) الزامی

یک item need صرفاً یک تاپل نام‌دار است و عملاً هر تاپلی کار خواهد کرد. علاوه بر دیگر Needها، یک نوع (type) نیز وجود دارد؛ به عنوان مثال می‌تواند به این شکل مشخص شود:

ItemNeed('update', 27, 'posts')
('update', 27, 'posts') # or like this

و این ممکن است مجوزی را برای به‌روزرسانی یک پست وبلاگ خاص توصیف کند. در عمل، توسعه‌دهنده در انتخاب هر قراردادی برای مجوزها آزاد است.

سیگنالی که هنگام تغییر هویت برای یک درخواست ارسال می‌شود.
سیگنالی که پس از مقداردهی اولیه هویت برای یک درخواست ارسال می‌شود.

در اینجا می‌توانید فهرست کامل تغییرات میان هر نسخه از Flask-Principal را مشاهده کنید.

منتشر شده در ۱۴ ژوئن ۲۰۱۳

  • افزوده شدن پشتیبانی از Python 3
  • حذف پشتیبانی از Python 2.5

منتشر شده در ۳ آوریل ۲۰۱۳

  • رفع باگ احتمالی در ارسال "anon" به عنوان نام کاربری توسط AnonymousIdentity
  • تغییر ویژگی name در Identity به id به منظور عمومی‌تر شدن

منتشر شده در ۱ فوریه ۲۰۱۳

  • افزودن متد __repr__ به کلاس‌های Identity و Permission
  • بهینه‌سازی متد _is_static_resource

    منتشر شده در ۴ سپتامبر ۲۰۱۲

•
افزودن متد init_app برای استفاده به همراه الگوی کارخانه (factory pattern).

منتشر شده در ۲۵ اوت ۲۰۱۲

•
به‌روزرسانی برای بررسی static_url_path در Flask 0.9

منتشر شده در ۱۶ اوت ۲۰۱۲

•
رفع باگ مربوط به پرتاب مجدد (re-raising) استثناها/ردیابی‌ها (tracebacks)

منتشر شده در ۲۰ ژوئن ۲۰۱۲

  • پشتیبانی از Python 2.5/GAE
  • ساختار جدید افزونه
  • افزودن گزینه ignore_static
  • به‌روزرسانی مستندات

توسعه اولیه توسط Ali Asfshar. مخزن اصلی https://bitbucket.org/aafshar/flask-principal-main

  • نمایه <>
  • نمایه ماژول‌ها <>
  • صفحه جستجو <>

Matt Wright

2012, Ali Afshar

December 15, 2025 0.4.0