
مكتبة ترخيص تصريحي مع لغة مجال خاصة (DSL) لكتابة قواعد السياسات والشروط والتخزين المؤقت. تتيح إدارة أذونات قابلة للتوسع ومبدأ DRY لتطبيقات Ruby.
DeclarativePolicy: مكتبة تفويض تعريفيةتوفر هذه المكتبة لغة مجال خاصة (DSL) لكتابة سياسات التفويض.
يمكن استخدامها لفصل المنطق عن الأذونات، وقد تم استخدامها على نطاق واسع في الإنتاج في GitLab.com.
المؤلف الأصلي لهذه المكتبة هو Jeanine Adkisson، وحقوق النشر محفوظة لـ GitLab.
أضف هذا السطر إلى ملف Gemfile الخاص بتطبيقك:
gem 'declarative_policy'
ثم قم بتنفيذ:
$ bundle install
أو قم بتثبيته بنفسك كما يلي:
$ gem install declarative_policy
require 'declarative_policy'
class User
attr_reader :name
def initialize(name:)
@name = name
end
end
class Vehicle
def initialize(owner:, trusted: [])
@owner = owner
@trusted = trusted
end
def owner?(user)
@owner.name == user.name
end
def trusted?(user)
@owner.name == user.name || @trusted.detect { |t| t.name == user.name }
end
end
class VehiclePolicy < DeclarativePolicy::Base
condition(:owns) { @subject.owner?(@user) }
condition(:trusted) { @subject.trusted?(@user) }
rule { owns }.enable :sell_vehicle
rule { trusted }.enable :drive_vehicle
end
jack = User.new(name: 'jack')
jill = User.new(name: 'jill')
jacks_vehicle = Vehicle.new(owner: jack, trusted: [jill])
jills_vehicle = Vehicle.new(owner: jill, trusted: [jack])
puts "Jack can drive Jack's vehicle? -> #{DeclarativePolicy.policy_for(jack, jacks_vehicle).can?(:drive_vehicle)}"
puts "Jack can drive Jill's vehicle? -> #{DeclarativePolicy.policy_for(jack, jills_vehicle).can?(:drive_vehicle)}"
puts "Jack can sell Jack's vehicle? -> #{DeclarativePolicy.policy_for(jack, jacks_vehicle).can?(:sell_vehicle)}"
puts "Jack can sell Jill's vehicle? -> #{DeclarativePolicy.policy_for(jack, jills_vehicle).can?(:sell_vehicle)}"
$ ruby example.rb
Jack can drive Jack's vehicle? -> true
Jack can drive Jill's vehicle? -> true
Jack can sell Jack's vehicle? -> true
Jack can sell Jill's vehicle? -> false
التجريد الأساسي لهذه المكتبة هو Policy. تجمع السياسات بين:
conditions) حول حالة العالمrules)توجد هذه المكتبة لتحديد القيمة الحقيقية للعبارات من الشكل:
User Predicate [Subject]
تتم مناقشة إعادة تسمية User إلى Actor و Subject إلى Resource في هذه المشكلة.
على سبيل المثال:
user :is_aliveuser :can_drive caruser :can_sell carيقوم بذلك عن طريق السماح لنا بربط Policy (مجموعة من القواعد حول أي العبارات صحيحة) بأشياء الجمل. تعتبر العبارة صحيحة إذا لم تمنعها أي قاعدة prevents، وكانت هناك قاعدة واحدة على الأقل تمكنها enables.
على سبيل المثال، تخيل أن لدينا نموذج بيانات يحتوي على مركبات ومستخدمين، ونريد معرفة ما إذا كان يمكن للمستخدم قيادة مركبة. نحتاج إلى VehiclePolicy:
class VehiclePolicy < DeclarativePolicy::Base
# relevant facts
condition(:owns) { @subject.owner == @user }
condition(:has_access_to) { @subject.owner.trusts?(@user) }
condition(:old_enough_to_drive) { @user.age >= laws.minimum_age }
condition(:has_driving_license) { @user.driving_license&.valid? }
# expensive rules can have 'score'. Higher scores are 'more expensive' to calculate
condition(:owns, score: 0) { @subject.owner == @user }
condition(:has_access_to, score: 3) { @subject.owner.trusts?(@user) }
condition(:intoxicated, score: 5) { @user.blood_alcohol > laws.max_blood_alcohol }
# conclusions we can draw:
rule { owns }.enable :drive_vehicle
rule { has_access_to }.enable :drive_vehicle
rule { ~old_enough_to_drive }.prevent :drive_vehicle
rule { intoxicated }.prevent :drive_vehicle
rule { ~has_driving_license }.prevent :drive_vehicle
# we can use methods to abstract common logic
def laws
@subject.registration.country.driving_laws
end
end
بعض النقاط التي يجب ملاحظتها: كان يمكننا كتابة هذا كقاعدة واحدة كبيرة (owns | has_access_to) & old_enough_to_drive & ~intoxicated & has_driving_license ولكن يمكننا رؤية بعض الميزات التي تجعل السياسات التعريفية قابلة للتوسع للأنظمة الكبيرة: يمكن تقسيم القواعد إلى عناصر صغيرة، وتكوينها في قواعد أكبر. يمكن إضافة شروط وقواعد جديدة في أي وقت.
ما يصعب رؤيته هو أن العديد من تحسينات الأداء تتم معالجتها بشفافية:
باختصار، تهدف هذه المكتبة إلى أن تكون تعريفية: نعلن القواعد المهمة، وتقوم المكتبة بترتيب كيفية تقييمها.
التخزين المؤقت هو ميزة قيمة بشكل خاص للسياسات. إذا أضفنا قواعد جديدة حول بيع مركبة، على سبيل المثال:
rule { owns }.enable :sell_vehicle
ثم يمكن مشاركة حقيقة الملكية بين استدعاءات مختلفة للسياسة، مما يوفر استدعاءات قاعدة البيانات وعمليات الإدخال/الإخراج المكلفة الأخرى.
يمكننا التحقق من قرار السياسة باستخدام:
cache = Session.current_session
policy = DeclarativePolicy.policy_for(user, car, cache: cache)
policy.can?(:drive_vehicle)
لمزيد من التفاصيل حول الاستخدام، راجع الوثائق.
بعد سحب المستودع، قم بتشغيل bundle install لتثبيت التبعيات. ثم قم بتشغيل rake spec لتشغيل الاختبارات. يمكنك أيضًا تشغيل bin/console للحصول على موجه تفاعلي يسمح لك بالتجربة.
لتثبيت هذه الجوهرة على جهازك المحلي، قم بتشغيل bundle exec rake install. لإصدار نسخة جديدة، قم بتحديث رقم الإصدار في version.rb، ثم قم بتشغيل bundle exec rake release، والذي سينشئ علامة git للإصدار، ويدفع التزامات git والعلامات، ويدفع ملف .gem إلى rubygems.org.
يمكن العثور على مزيد من التفاصيل حول السياسات والأدوار المخصصة في الصفحات التالية:
تقارير الأخطاء وطلبات الدمج مرحب بها على GitLab على https://gitlab.com/gitlab-org/ruby/gems/declarative-policy. هذا المشروع يهدف إلى أن يكون مساحة آمنة ومرحبة للتعاون، ومن المتوقع أن يلتزم المساهمون بـ مدونة قواعد سلوك GitLab.
نقوم بإصدار declarative_policy على أساس مخصص. لا يوجد انتظام لموعد الإصدار، نحن نصدر فقط عندما نقوم بتغيير - بغض النظر عن حجم التغيير.
لإصدار نسخة جديدة:
declarative_policy Ruby إلى الإصدار الجديد أيضًا.الجوهرة متاحة كمصدر مفتوح بموجب شروط رخصة MIT.
من المتوقع أن يتبع كل من يتفاعل مع قاعدة شفرة مشروع DeclarativePolicy، ومتتبعات المشكلات، وغرف الدردشة، والقوائم البريدية مدونة قواعد السلوك.