diff --git a/README.md b/README.md index ede374f..3ac5ffb 100644 --- a/README.md +++ b/README.md @@ -28,38 +28,40 @@ Based on [Officially Supported Databases](https://docs.djangoproject.com/en/3.0/ pip install casbin-django-orm-adapter ``` -Add `casbin_adapter` to your `INSTALLED_APPS` +Add `casbin_adapter.apps.CasbinAdapterConfig` to your `INSTALLED_APPS` ```python +# settings.py +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + INSTALLED_APPS = [ ... - 'casbin_adapter', + 'casbin_adapter.apps.CasbinAdapterConfig', ... ] + +CASBIN_MODEL = os.path.join(BASE_DIR, 'casbin.conf') ``` -To run schema migration, execute `python manage.py migrate casbin_adapter +To run schema migration, execute `python manage.py migrate casbin_adapter` ## Simple Example ```python -import casbin -from casbin_adapter.adapter import Adapter - -adapter = Adapter() - -e = casbin.Enforcer('path/to/model.conf', adapter, True) - -sub = "alice" # the user that wants to access a resource. -obj = "data1" # the resource that is going to be accessed. -act = "read" # the operation that the user performs on the resource. - -if e.enforce(sub, obj, act): - # permit alice to read data1casbin_django_orm_adapter - pass -else: - # deny the request, show an error - pass +# views.py +from casbin_adapter.enforcer import enforcer + +def hello(request): + sub = "alice" # the user that wants to access a resource. + obj = "data1" # the resource that is going to be accessed. + act = "read" # the operation that the user performs on the resource. + + if e.enforce(sub, obj, act): + # permit alice to read data1casbin_django_orm_adapter + pass + else: + # deny the request, show an error + pass ``` ### Getting Help diff --git a/casbin_adapter/adapter.py b/casbin_adapter/adapter.py index f46b142..7ffd23d 100644 --- a/casbin_adapter/adapter.py +++ b/casbin_adapter/adapter.py @@ -1,3 +1,4 @@ +from django.conf import settings from casbin import persist from .models import CasbinRule diff --git a/casbin_adapter/apps.py b/casbin_adapter/apps.py index 1d19f74..f93ad4d 100644 --- a/casbin_adapter/apps.py +++ b/casbin_adapter/apps.py @@ -1,5 +1,12 @@ from django.apps import AppConfig +from django.db import connection +from django.db.utils import OperationalError, ProgrammingError class CasbinAdapterConfig(AppConfig): name = 'casbin_adapter' + + def ready(self): + from .enforcer import initialize_enforcer + initialize_enforcer() + diff --git a/casbin_adapter/enforcer.py b/casbin_adapter/enforcer.py new file mode 100644 index 0000000..65cfcbc --- /dev/null +++ b/casbin_adapter/enforcer.py @@ -0,0 +1,64 @@ +from django.conf import settings +from django.db import connection +from django.db.utils import OperationalError, ProgrammingError + +from casbin import Enforcer + +from .adapter import Adapter + + +class ProxyEnforcer(Enforcer): + _initialized = False + + def __init__(self, *args, **kwargs): + if self._initialized: + super().__init__(*args, **kwargs) + + def _load(self): + if self._initialized == False: + self._initialized = True + model = getattr(settings, 'CASBIN_MODEL') + enable_log = getattr(settings, 'CASBIN_LOG_ENABLED', False) + adapter = Adapter() + + super().__init__(model, adapter, enable_log) + + watcher = getattr(settings, 'CASBIN_WATCHER', None) + if watcher: + self.set_watcher(watcher) + + role_manager = getattr(settings, 'CASBIN_ROLE_MANAGER', None) + if role_manager: + self.set_role_manager(role_manager) + + def __getattribute__(self, name): + safe_methods = ['__init__', '_load', '_initialized'] + if not super().__getattribute__('_initialized') and name not in safe_methods: + initialize_enforcer() + if not super().__getattribute__('_initialized'): + raise Exception(( + "Calling enforcer attributes before django registry is ready. " + "Prevent making any calls to the enforcer on import/startup" + )) + + return super().__getattribute__(name) + + +enforcer = ProxyEnforcer() + + +def initialize_enforcer(): + try: + with connection.cursor() as cursor: + cursor.execute( + """ + SELECT app, name applied FROM django_migrations + WHERE app = 'casbin_adapter' AND name = '0001_initial'; + """ + ) + row = cursor.fetchone() + if row: + enforcer._load() + except (OperationalError, ProgrammingError): + pass + diff --git a/tests/settings.py b/tests/settings.py index 9c4e87f..9040917 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,3 +1,7 @@ +import os + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + SECRET_KEY = 'not-a-production-secret' INSTALLED_APPS = [ @@ -7,7 +11,7 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - "casbin_adapter", + "casbin_adapter.apps.CasbinAdapterConfig", "tests", ] @@ -37,3 +41,5 @@ ] DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}} + +CASBIN_MODEL = os.path.join(BASE_DIR, 'tests', 'rbac_model.conf')