GithubHelp home page GithubHelp logo

rsinger86 / drf-access-policy Goto Github PK

View Code? Open in Web Editor NEW
439.0 439.0 47.0 2.06 MB

Declarative access policies/permissions modeled after AWS' IAM policies.

Home Page: https://rsinger86.github.io/drf-access-policy/

License: MIT License

Python 100.00%
access-control authorization declarative django django-rest-framework iam permissions

drf-access-policy's People

Contributors

adamsteele-city avatar alessandro-mariotti-zupit avatar barnabasszabolcs avatar bo5o avatar bradydean avatar dependabot[bot] avatar filwaline avatar gianpieropa avatar hardntrash avatar helderlgoliveira avatar heng-zhang-20 avatar honakerm avatar jamesonnetworks avatar jt501 avatar killianmeersman avatar nikeshyad avatar oguzhancelikarslan avatar rsinger86 avatar sarthikg avatar tanonl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

drf-access-policy's Issues

Why "principal: authenticated" is "not anonymous" instead of "is authenticated"?

Hey @rsinger86,

After reading the code a bit, I stumbled across the code for matching principal on statements.
I found out the code at access_policy.py line 142 that the "authenticated" value for principal checks that the user is not anonymous instead of actually whether it is authenticated or not.

Out of curiosity, why is that implemented like so and not using built-in "is_authenticated"?
Is there a difference?

Thank you!

Inconsistent expression parsing

Hello, there is a problem with the BoolOperand.__new__ method. The returned operand would not properly work in such a case:

  1. Make a custom policy condition checking method, i.e. check_something.
  2. Use it in a form: check_something:{parent}.attribute_one.attribute_two inside a statement's condition and it works.
  3. Use it again in a condition_expression in a form not check_something:{parent}.attribute_one.attribute_two and it will silently fail, because characters like { and } are not recognized properly.

return TRUE | FALSE | Word(alphanums + '_:.*', max=256)

It should rather be:

class BoolOperand(object):
    def __new__(cls):
        return TRUE | FALSE | Combine(Word(alphanums + "_", max=256) + ":" + Word(printables, max=256))

FieldAccessMixin with read_only=True serializer requires request attribute

Suppose i have following serializer with FieldAccessMixin:

class OrganizationSerializerV1(CustomFieldAccessMixin, serializers.ModelSerializer):

    pass

And i have this serializer in another serializer with read_only=True attribute:

class WorkOrganizationSerializerV1(serializers.ModelSerializer):

    organizations_ids = serializers.PrimaryKeyRelatedField(
        write_only=True,
        many=True,
        queryset=OrganizationV1.objects.all(),
        source="organizations",
    )
    organizations = OrganizationSerializerV1(read_only=True, many=True)

So i always got an exception that i need a context with request in it but serializer is read only.
Is there any way that i can handle it?
Maybe we need something like that in source code?

class FieldAccessMixin(object):

    def __init__(self, *args, **kwargs):
        self.serializer_context = kwargs.get("context", {})
        super().__init__(*args, **kwargs)
        if self.read_only is False:
            self._apply_fields_access()
            self._apply_deprecated_field_permissions()

Any ideas to enable a similar logic on frontend code

Amazing package! ⭐

Now, when you have such a fine grained backend permissioning system, it becomes super helpful to be able to translate that into the frontend code (Javascript) so that you can control which actions to show/allow based on the expected backend permission. For instance, if you know that certain role doesn't have access to perform a "create" in a given viewset, it would be useful to know that in the frontend so as to block/hide the respective create button when the user has that role.

Any ideas on how something like this could be achievable?

Again, fantastic package!

URL path variable in custom condition

This is more a question than an issue.
I have the following custom action on my ModelViewSet:

@action(methods=["post", "delete"], detail=True, url_path="assign/(?P<userid>[^/.]+)")
def assign(self, request, pk=None, userid=None):

And on the AccessPolicy I have a method:

def self_or_manager(self, request, view, action) -> bool:

I want to access the userid in my condition method. Is this possible?

Thanks,
Tobias

How to apply access policies to class based views

I would like to apply the class-based views, not to ViewSets or function-based views.
For example I would like to use the access policies to Views derived from rest_framework.generics, such as RetrieveAPIView.

I managed to apply the access policy when using "action": ["*"] for the RetrieveAPIView view, but if I use "action": ["list", "retrieve", "get"] it does not work, so I don't know how to use finely-tuned access policies in this context.

Thanks in advance for your help :)

Built-in conditions: user_is X, is_read, client_ip

Rather than having to always write a custom method when a statement needs to examine the contextual details of the request/user/object, some built-in conditions could be provided. I think the syntax would be to use a dictionary for these built-in checks, and have string values continue to reference a custom method on the policy.

{
   "action": "*",
   "principal": "*",
   "condition": { "<condition_type>": "<value_option>" }
}

There could be three to start with:

{
   "action": "*",
   "principal": "*",
   "condition": { "is_read": false }  # whether the request method is HEAD, GET or OPTIONS
}

{
   "action": "*",
   "principal": "*",
   "condition": { "client_ip": "203.0.113.0/24" }  # whether the requester's IP matches 
}

{
   "action": "*",
   "principal": "*",
   "condition": { "user_is": "owner" }  # Whether a field on the object instance (from view.get_object()) is equal to the request user
}

{
   "action": "*",
   "principal": "*",
   "condition": { "is_authenticated": True } # whether the user is authenticated
}

AWS IAM provides a much more feature-rich version of this: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html

However, since we have the flexibility to write custom methods, I think it's best to only cover most common and simple cases with built-in conditions.

Thanks for writing this library ❤️

Thanks for writing this library ❤️

This looks interesting. I have been searching for access related library for DRF that handles even object level and multi tenancy.

Order of inheritance of AccessViewSetMixin in Viewset matters?

I just installed this library and faced this issue
I am really new in this my apologies if it's a lame issue

Result: Access Policy doesn't work
class ArticleViewSet(ModelViewSet, AccessViewSetMixin):
access_policy = ArticleAccessPolicy
...

Result: Access Policy works!
class ArticleViewSet(AccessViewSetMixin, ModelViewSet):
access_policy = ArticleAccessPolicy
...

Result: Access Policy works!
class ArticleViewSet(ModelViewSet):
permission_classes = [ArticleAccessPolicy, ]
...

[Question] Dealing with unnecessary repetition of DB hits...

So,
I have a Membership model, with 3 roles - viewer, member, manager
I created a group for all users that aren't django staff or admins to filter out some processing
My problem is I have to repeat the same calls to get the membership instance to check for the permission.
(A member can be a part of multiple schedules)

class ShiftAccessPolicy(BaseAccessPolicy):

    statements = [
        {
            "action": "*",
            "principal": ["admin", "staff"],
            "effect": "allow",
        },
        {
            "action": ["list", "retrieve"],
            "principal": "group:auth_user",
            "effect": "allow",
            "condition": "is_schedule_member"
        },
        {
            "action": "create",
            "principal": "group:auth_user",
            "effect": "allow",
            "condition_expression": "is_not_viewer_only"
        },
        {
            "action": [ "archive"],
            "principal": "group:auth_user",
            "effect": "allow",
            "condition_expression": "is_schedule_manager"
        }
    ]

    def is_schedule_manager(self, request, view, action):
        membership = request.user.member.membership_set.filter(schedule__id=view.kwargs.get('schedule_pk'))
        if membership.exists():
            membership = membership[0]
            return Roles.SCHEDULE_MANAGER == membership.role
        else:
            return False

    def is_schedule_member(self, request, view, action):
        membership = request.user.member.membership_set.filter(schedule__id=view.kwargs.get('schedule_pk'))
        return membership.exists()

    def is_viewer_only(self, request, view, action):
        membership = request.user.member.membership_set.filter(schedule__id=view.kwargs.get('schedule_pk'))
        if membership.exists():
            membership = membership[0]
            return Roles.VIEWER != membership.role
        else:
            return False

As can be seen - each time I have to get the membership...
3 questions came into mind:

  1. Is there a better way to use the access policy module that I'm missing?
  2. Should we implement a "shared" instance for the types of cases?
  3. in the case that I would use the same condition (lets say is_schedule_member would have been called multiple times) shouldn't we cache the result from the first time it was called?

Introduction of support for condition expressions is a breaking change on existing condition strings

First of all, thanks for a nice project, it's neat to be able to dynamically load json of access policy statements to apply highly configurable permissions with custom condition functions. We use this in a real-world application with real customers with real security implications.

We recently upgraded to 0.8.7 and discovered that a load of permission rules were now being ignored (so people who aren't supposed to be allowed to do things can now do them). Reason is this change: 199d53a It's trying to be clever and parsing the condition string, which means the wrong value is passed to a condition function. For example we had this condition string working fine in 0.7.0

"can_edit_event_attribute:start_date,end_date:EDIT_EVENT_DATES"

can_edit_event_attribute is a custom function on the access policy which takes the string argument start_date,end_date:EDIT_EVENT_DATES splits by colon to get attributes list which is comma separated, and then permission name. So logically this is saying "If you want to change the start_date or end_date fields in a json payload to this endpoint, you need the permission object EDIT_EVENT_DATES. This worked very nicely because when customers said "Hey, I also want to restrict the duration field with that same permission", we could just update the string in policy json to start_date,end_date,duration:EDIT_EVENT_DATES and bingo it works with config not code change. We have adopted this pattern of condition strings being a colon separated bunch of clauses which can themselves be comma separated as a way of passing arguments into the custom condition functions and it's powerful and convenient.

But now that the condition string is being parsed, the comma is making that string get truncated and all that is passed to the can_edit_event_attribute condition function is incorrectly 'start_date' rather than 'start_date,end_date:EDIT_EVENT_DATES'.

I appreciate being able to do logical expressions in the condition strings is useful, but it is not backwards compatible and could be breaking lots of other clients too (who might not notice that users can now subvert the permission rules).

I see the most recent change added '*' character to what counts as a word for the tokenizer. So the easy fix here would be adding comma too and that would fix my use case. But other clients could have condition strings containing spaces or the string OR if they'd built their own parser of condition strings so that's not really a good general fix. The crux of the issue is that it's invalid to apply a parser/tokenizer to what used to be a simple string and clients were relying on receiving verbatim. Maybe the solution is to introduce a new property in the json 'condition_expression' that supports the parsing, and 'condition' does no parsing and passes the string verbatim to avoid breaking clients relying on that prior behaviour.

How to implement field based access as mentioned in the docs?

Great package 🥇 Much clearer adn more flexible than the DRF build in permission system.

I appreciate the feature Field Level Permissions but can't get it to work fully.

From the docs:
Often, depending on the user, not all fields should be visible or only a subset should be writable.

The docs example shows how to remove a field at all, but I can't figure out how to achieve making one or more fields read_only. Even lurking at the source does not help me figure it out.

Would it be possible to explain that a little more or giving a hint how to achieve "only a subset should be writable"?

Thanks.

Version 0.8.5 breaks conditions with `*` as params

I use my conditions with this behaviour when I want to allow any action based in some logics requireds in my application:

class MyPolicy(AccessPolicy):

    statements = [
        {
            'action': '*',
            'principal': 'authenticated',
            'effect': 'allow',
            'condition': ['check_permissions:*']  # this could be `check_permissions:can_add,can_read,can_this,can_that` but to prevent write all, I use *
        }
    ]

    def check_permissions(self, request, view, action, permissions)
        if permissions == '*':
             return True
        else:
             # Checking others conditions based on permissions param like 'can_add,can_read'

check_permissions:*, this is a condition to allow any action based on values in Database and others logics.

But with 0.8.5 its breaks my implementation because it is passing None to permissions param in check_permissions

I was forced to downgrade to 0.8.1 to make my app works again and I we can't refactoring entire application because of the time and cost we will have.

So I'm justin reporting the issue that we faced with this version

`view.get_object()` raises AssertionError inside condition method

Hi there.

I'm using view.get_object() as shown in the documentation but in certain cases there is an assertion failing (lookup_url_kwarg in self.kwargs) inside of get_object(). In the example I share here, it's on the list action, where the url follows the pattern: /quiz/?course=<pk>. In another case, however, it's when I use the create action on a different model.

Expected view QuizViewSet to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly.

Here is my policy:

statements = [
  {
    'action': ['retrieve'],
    'principal': 'authenticated',
    'effect': 'allow',
    'condition_expression': 'is_in_course and accessible_if_student'
  }
]

The condition methods:

def is_in_course(self, request, view, action):
  user = request.user
  quiz = view.get_object()  # raises AssertionError on list action
  return user in course.admins.all() or user in course.instructors.all() or user in course.graders.all() \
    or user in course.students.all() or user in course.guests.all()

def accessible_if_student(self, request, view, action):
  user = request.user
  quiz = view.get_object()
  course = quiz.course

  if user in course.students.all():
    return True
  return quiz.published and user in quiz.students.all()

So clearly I'm missing the pk but I'm not exactly sure how I'm supposed to provide that. Any ideas?

ViewSet actions on policy doesnot work properly

below is my policy.

from rest_access_policy import AccessPolicy

class SearchAccessPolicy(AccessPolicy):
statements = [
{
"action": ["person"],
"principal": ["authenticated"],
"effect": "allow",
}
]

and i also have a function in a ViewSet-based class.
@action(methods=['POST'], detail=False, name='person')
def person(self, request):
...
...
..

Even though i try to make a request as logged in user. it gives 403 error. How can i solve this problem?

Include additional information in permission denied response

First things first, this is an awesome package and I love it. Thanks!

Is there a way to give some information about which permission check failed in the resulting 403 response?

I'd like to add a message in the permission denied response depending on what statement condition returns False in the permission checks. For example, given the following statements:

        {
            "action": ["list"],
            "principal": ["authenticated"],
            "effect": "allow",
            "condition_expression": "has_teacher_privileges:manage_events or requested_own_participations",
        },
        {
            "action": ["create"],
            "principal": ["authenticated"],
            "effect": "allow",
            "condition_expression": "can_participate",
        }

If the method can_participate returns False, I'd like the response body to maybe look something like:

{"detail": "You cannot participate"}

as opposed to the default for Django;

{"detail": "You do not have permission to perform this action."}

Is this in any way possible?

Object level permissions

https://www.django-rest-framework.org/api-guide/permissions/#object-level-permissions

def get_object(self):
    obj = get_object_or_404(self.get_queryset(), pk=self.kwargs["pk"])
    self.check_object_permissions(self.request, obj)
    return obj

So get_object already has check_permissions call. So it would be nice to impelement has_object_permission (https://github.com/encode/django-rest-framework/blob/master/rest_framework/permissions.py#L112).

It will have the same logic as has_permission but provide additional attribute object into conditions functions.

Signature of conditions would be f(self, request, view, action, object=None) -> bool. And object-level conditions should have this check:

if object is None:
    return True

P.S. If you agree with this I could impelement it and create PR.

scope_queryset not working

I have ModelViewSet with permission_class and override method scope_querset inside, but it isn't called (if not override, parent's method not called too). what could be?

Allow to pass BasePermission subclasses into `condition` statement

There is few default classes like IsAuthorized but there is no reason to create custom method:

def is_authorized(self, *args, **kwargs):
    return IsAuthorized.has_permission(*args, **kwargs)

Istead just add check of condition value (isclass(cond) and issubclass(cond, BasePermission) or isinstance(cond, BasePermission)) so it can be pass directly:

"condition": permissions.IsAuthorized

Should raise method not allowed 405 rather than permission denied 403

While a request has undefined (method, action) combination , this module will raise 403, I think response 405 is much more appropriate.

For example, I defined an extra action in viewset:

# And make this public in AP's statements
@action(detail=False, methods=['post'])
def foo(self, request):
    ...

If any one try GET /foo, he will get 403 rather 405.

After study source code, I found this happened because AccessPolicy will return False while can't find a match statement. But django-restframework already handled such case.
Maybe you should let it go, and let DRF did the rest?

Default value "effect": "allow"

I think that "effect" could have a default value of "allow" because

  1. for me it was first a bit confusing
  2. we typically want to declare "allow"'s.

Support db statements

Support store list statements in database
to make it easier to create and edit policies without changing the source code 🙂

ModuleNotFoundError: No module named 'pyparsing'

Looks like pyparsing is not being installed with drf-access-policy:

$ pip install django djangorestframework drf-access-policy               
Defaulting to user installation because normal site-packages is not writeable
Collecting django
  Using cached Django-3.1.5-py3-none-any.whl (7.8 MB)
Collecting djangorestframework
  Using cached djangorestframework-3.12.2-py3-none-any.whl (957 kB)
Processing ./.cache/pip/wheels/55/b4/74/357bd96251493a1be8fec4cdee10e54399b7f8bcbe864d20ce/drf_access_policy-0.8.5-py3-none-any.whl
Requirement already satisfied: sqlparse>=0.2.2 in ./.local/lib/python3.9/site-packages (from django) (0.4.1)
Requirement already satisfied: pytz in /usr/lib/python3.9/site-packages (from django) (2020.4)
Requirement already satisfied: asgiref<4,>=3.2.10 in ./.local/lib/python3.9/site-packages (from django) (3.3.1)
Installing collected packages: django, djangorestframework, drf-access-policy
Successfully installed django-3.1.5 djangorestframework-3.12.2 drf-access-policy-0.8.5

$ python3
Python 3.9.0 (default, Oct  6 2020, 00:00:00) 
[GCC 10.2.1 20200826 (Red Hat 10.2.1-3)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from rest_access_policy import AccessPolicy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/daviddavis/.local/lib/python3.9/site-packages/rest_access_policy/__init__.py", line 2, in <module>
    from .access_policy import AccessPolicy
  File "/home/daviddavis/.local/lib/python3.9/site-packages/rest_access_policy/access_policy.py", line 6, in <module>
    from pyparsing import infixNotation, opAssoc
ModuleNotFoundError: No module named 'pyparsing'

[BUG] bool type error

I believe I have found a bug in drf-access-policy. During some requests I receive the following 500 error:

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 497, in dispatch
    self.initial(request, *args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 415, in initial
    self.check_permissions(request)
  File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 332, in check_permissions
    if not permission.has_permission(request, self):
  File "/usr/local/lib/python3.8/site-packages/rest_access_policy/access_policy.py", line 26, in has_permission
    return self._evaluate_statements(statements, request, view, action)
  File "/usr/local/lib/python3.8/site-packages/rest_access_policy/access_policy.py", line 62, in _evaluate_statements
    matched = self._get_statements_matching_context_conditions(
  File "/usr/local/lib/python3.8/site-packages/rest_access_policy/access_policy.py", line 181, in _get_statements_matching_context_conditions
    passed = bool(boolExpr.parseString(condition)[0])
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1943, in parseString
    loc, tokens = self._parse(instring, 0)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4462, in parseImpl
    return self.expr._parse(instring, loc, doActions, callPreParse=False)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4462, in parseImpl
    return self.expr._parse(instring, loc, doActions, callPreParse=False)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4254, in parseImpl
    ret = e._parse(instring, loc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4462, in parseImpl
    return self.expr._parse(instring, loc, doActions, callPreParse=False)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4254, in parseImpl
    ret = e._parse(instring, loc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4462, in parseImpl
    return self.expr._parse(instring, loc, doActions, callPreParse=False)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4254, in parseImpl
    ret = e._parse(instring, loc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4069, in parseImpl
    loc, exprtokens = e._parse(instring, loc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4462, in parseImpl
    return self.expr._parse(instring, loc, doActions, callPreParse=False)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4462, in parseImpl
    return self.expr._parse(instring, loc, doActions, callPreParse=False)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4254, in parseImpl
    ret = e._parse(instring, loc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4069, in parseImpl
    loc, exprtokens = e._parse(instring, loc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4462, in parseImpl
    return self.expr._parse(instring, loc, doActions, callPreParse=False)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4052, in parseImpl
    loc, resultlist = self.exprs[0]._parse(instring, loc, doActions, callPreParse=False)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4462, in parseImpl
    return self.expr._parse(instring, loc, doActions, callPreParse=False)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4254, in parseImpl
    ret = e._parse(instring, loc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4462, in parseImpl
    return self.expr._parse(instring, loc, doActions, callPreParse=False)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1683, in _parseNoCache
    loc, tokens = self.parseImpl(instring, preloc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 4254, in parseImpl
    ret = e._parse(instring, loc, doActions)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1716, in _parseNoCache
    tokens = fn(instring, tokensStart, retTokens)
  File "/usr/local/lib/python3.8/site-packages/pyparsing.py", line 1316, in wrapper
    ret = func(*args[limit[0]:])
TypeError: __init__() missing 1 required positional argument: 't'

The error seems to stem from File "/usr/local/lib/python3.8/site-packages/rest_access_policy/access_policy.py", line 181, in _get_statements_matching_context_conditions

Allow OR operation for the condition statement

I love this package. It is exactly what I needed. Simple and Clear

I have maybe one suggestion. Currently, the condition statement is basically a AND operation, all conditions in the list need to be True. It would good also to be able to have an OR operation.
e.g. "condition" : ["balance_is_positive", "account_is_blocked | is_manager"]
e.g. "condition" : "balance_is_positive & (is_manager | account_is_blocked)"

user.id error

Errors occur when overriding PK on a field with a different name. For example, "uuid".
The reason for this is elif self.id_prefix + str(user.id) in principals: in the file "access_policy.py". Please fix it to str(user.pk).

Thank you for earlier.

[BUG] <lambda>() missing 1 required positional argument: 'token'

I had this error in production a couple of times.

The statement that caused it is the following:

{
            "action": ["retrieve", "update", "partial_update"],
            "principal": ["authenticated"],
            "effect": "deny",
            "condition_expression": "not has_teacher_privileges:assess_participations and not is_slot_in_scope",
}
   def has_teacher_privileges(self, request, view, action, privilege):
        from courses.models import Course
        from courses.views import CourseViewSet

        course_pk = (
            view.kwargs.get("pk")
            if isinstance(view, CourseViewSet)
            else view.kwargs.get("course_pk")  # nested view
        )

        try:
            course = Course.objects.get(pk=course_pk)
        except ValueError:
            return False

        return check_privilege(request.user, course, privilege)

    def is_slot_in_scope(self, request, view, action):
        slot = view.get_object()
        return slot.is_in_scope()

This condition expression works most times, but sometimes it errors like I described. I haven't yet been able to identify what causes this, but I don't believe it to be something that has to do with my code. It looks like a bug in drf-access-policy.

Full stack trace:

 Traceback (most recent call last):
   File "/app/.heroku/python/lib/python3.8/site-packages/asgiref/sync.py", line 451, in thread_handler
     raise exc_info[1]
   File "/app/.heroku/python/lib/python3.8/site-packages/django/core/handlers/exception.py", line 42, in inner
     response = await get_response(request)
   File "/app/.heroku/python/lib/python3.8/site-packages/django/core/handlers/base.py", line 253, in _get_response_async
     response = await wrapped_callback(
   File "/app/.heroku/python/lib/python3.8/site-packages/asgiref/sync.py", line 414, in __call__
     ret = await asyncio.wait_for(future, timeout=None)
   File "/app/.heroku/python/lib/python3.8/asyncio/tasks.py", line 455, in wait_for
     return await fut
   File "/app/.heroku/python/lib/python3.8/site-packages/asgiref/current_thread_executor.py", line 22, in run
     result = self.fn(*self.args, **self.kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/asgiref/sync.py", line 455, in thread_handler
     return func(*args, **kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/sentry_sdk/integrations/django/views.py", line 67, in sentry_wrapped_callback
     return callback(request, *args, **kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
     return view_func(*args, **kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/viewsets.py", line 125, in view
     return self.dispatch(request, *args, **kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
     response = self.handle_exception(exc)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
     self.raise_uncaught_exception(exc)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
     raise exc
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/views.py", line 497, in dispatch
     self.initial(request, *args, **kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/sentry_sdk/integrations/django/__init__.py", line 271, in sentry_patched_drf_initial
     return old_drf_initial(self, request, *args, **kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/views.py", line 415, in initial
     self.check_permissions(request)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/views.py", line 332, in check_permissions
     if not permission.has_permission(request, self):
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_access_policy/access_policy.py", line 52, in has_permission
     allowed = self._evaluate_statements(statements, request, view, action)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_access_policy/access_policy.py", line 94, in _evaluate_statements
     matched = self._get_statements_matching_conditions(
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_access_policy/access_policy.py", line 214, in _get_statements_matching_conditions
     passed = bool(boolExpr.parseString(condition)[0])
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 1124, in parse_string
     loc, tokens = self._parse(instring, 0)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 5203, in parseImpl
     return super().parseImpl(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 5203, in parseImpl
     return super().parseImpl(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4091, in parseImpl
     return e._parse(
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 5203, in parseImpl
     return super().parseImpl(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4091, in parseImpl
     return e._parse(
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 3863, in parseImpl
     loc, exprtokens = e._parse(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 3841, in parseImpl
     loc, resultlist = self.exprs[0]._parse(
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 5203, in parseImpl
     return super().parseImpl(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4091, in parseImpl
     return e._parse(
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 3863, in parseImpl
     loc, exprtokens = e._parse(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 3863, in parseImpl
     loc, exprtokens = e._parse(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 5203, in parseImpl
     return super().parseImpl(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4091, in parseImpl
     return e._parse(
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 849, in _parseNoCache
     tokens = fn(instring, tokens_start, ret_tokens)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 285, in wrapper
     ret = func(*args[limit:])
 TypeError: <lambda>() missing 1 required positional argument: 'token'
 Internal Server Error: /courses/7/events/83P423W/participations/415/slots/3984/
 Traceback (most recent call last):
   File "/app/.heroku/python/lib/python3.8/site-packages/asgiref/sync.py", line 451, in thread_handler
     raise exc_info[1]
   File "/app/.heroku/python/lib/python3.8/site-packages/django/core/handlers/exception.py", line 42, in inner
     response = await get_response(request)
   File "/app/.heroku/python/lib/python3.8/site-packages/django/core/handlers/base.py", line 253, in _get_response_async
     response = await wrapped_callback(
   File "/app/.heroku/python/lib/python3.8/site-packages/asgiref/sync.py", line 414, in __call__
     ret = await asyncio.wait_for(future, timeout=None)
   File "/app/.heroku/python/lib/python3.8/asyncio/tasks.py", line 455, in wait_for
     return await fut
   File "/app/.heroku/python/lib/python3.8/site-packages/asgiref/current_thread_executor.py", line 22, in run
     result = self.fn(*self.args, **self.kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/asgiref/sync.py", line 455, in thread_handler
     return func(*args, **kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/sentry_sdk/integrations/django/views.py", line 67, in sentry_wrapped_callback
     return callback(request, *args, **kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
     return view_func(*args, **kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/viewsets.py", line 125, in view
     return self.dispatch(request, *args, **kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
     response = self.handle_exception(exc)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
     self.raise_uncaught_exception(exc)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
     raise exc
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/views.py", line 497, in dispatch
     self.initial(request, *args, **kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/sentry_sdk/integrations/django/__init__.py", line 271, in sentry_patched_drf_initial
     return old_drf_initial(self, request, *args, **kwargs)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/views.py", line 415, in initial
     self.check_permissions(request)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_framework/views.py", line 332, in check_permissions
     if not permission.has_permission(request, self):
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_access_policy/access_policy.py", line 52, in has_permission
     allowed = self._evaluate_statements(statements, request, view, action)
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_access_policy/access_policy.py", line 94, in _evaluate_statements
     matched = self._get_statements_matching_conditions(
   File "/app/.heroku/python/lib/python3.8/site-packages/rest_access_policy/access_policy.py", line 214, in _get_statements_matching_conditions
     passed = bool(boolExpr.parseString(condition)[0])
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 1124, in parse_string
     loc, tokens = self._parse(instring, 0)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 5203, in parseImpl
     return super().parseImpl(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 5203, in parseImpl
     return super().parseImpl(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4091, in parseImpl
     return e._parse(
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 5203, in parseImpl
     return super().parseImpl(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4091, in parseImpl
     return e._parse(
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 3863, in parseImpl
     loc, exprtokens = e._parse(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 3863, in parseImpl
     loc, exprtokens = e._parse(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4767, in parseImpl
     loc, tokens = self_expr_parse(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 3863, in parseImpl
     loc, exprtokens = e._parse(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 5203, in parseImpl
     return super().parseImpl(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4091, in parseImpl
     return e._parse(
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 3863, in parseImpl
     loc, exprtokens = e._parse(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 3863, in parseImpl
     loc, exprtokens = e._parse(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 5203, in parseImpl
     return super().parseImpl(instring, loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4352, in parseImpl
     return self.expr._parse(instring, loc, doActions, callPreParse=False)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 810, in _parseNoCache
     loc, tokens = self.parseImpl(instring, pre_loc, doActions)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 4091, in parseImpl
     return e._parse(
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 849, in _parseNoCache
     tokens = fn(instring, tokens_start, ret_tokens)
   File "/app/.heroku/python/lib/python3.8/site-packages/pyparsing/core.py", line 285, in wrapper
     ret = func(*args[limit:])
 TypeError: <lambda>() missing 1 required positional argument: 'token'

What caused this? Is there any issue with the condition(s) I wrote?

Response philosophy based on type of failure

Pardon me if I got a bit excited about this package. This issue is basically a philosophical discussion that goes outside the scope of this project.

I know that DRF's custom permissions are rather limited in that you can only return a boolean and DRF will take care of the response. Thinking in how you're combining static checks with more dynamic checks, it would be great if the error response could be made more explicit depending on the type of failure. Let me explain myself.

I consider a static check to be something that is unlikely to change over time. User or role based permissions are a good example of those, as permissions attached to users/roles do not depend on the state of the system. A dynamic check would be something that depends on the state of the system, that is, something that would fit in a "condition" of the statement.

To me, it's not philosophically correct to treat a failure on each situation in the same way, that is, it's not fair to just give a plain 403 on each and all situations. If a user doesn't have permission to perform certain action based on the user itself or its roles, that's a typical 403. However, if the failure comes from a condition that tested positive in the current request, but is likely to test negative when the system changes (e.g. is_happy_hour), then, a different type of error would be expected, in order to tell the user that the 403 is not permanent. If not a different response status, at least a different level of details in the error response body.

Are you making such distinctions in your own uses of this package? Do you know of any DRF hook that would make something like this possible, other than possibly annotating the request and capturing the exception on the way out via middleware?

Typo

Umm... I just wanted to tell you that you have a typo in the first line of

site_name: Dango REST - Access Policy

It says Dango REST :)

Should be Django, obviously.

get_user_group_values is not efficient

def get_user_group_values(self, user) -> List[str]:

    def get_user_group_values(self, user) -> List[str]:
        return list(user.groups.values_list("name", flat=True))

AccessPolicy heavily rely on compute current user's groups, but this implementation seems bad... It will hit database every time(reported by django-debug-toolbar), rather than using cache. For example, User.objects.prefetch_related('groups').all() take no credits here.

Simply change a bit will make a big progress:

# prefetch related somewhere
# user = User.objects.prefetch_related('groups').get(...)
...
    def get_user_group_values(self, user) -> List[str]:
        return [g.name for g in user.groups.all()]

From now on, no matter how many calls of get_user_group_values, database only get two queries(prefetch take one extra query). If User model haven't prefetch with Group, things still keep the same, it won't get worse.

And there is a better solution, using cache_property, but this need to define a method in model, which can't not handled by assess policy.

Support dynamic response message and code

I need to change the response message and code dynamically to tell the front-end why it happened.
The message and the code can be defined by defining the message and code attribute, but they are static and it seems that there is no implementation to change them dynamically.

Enforce access policy through PrimaryKeyRelatedField

I have two ModelViewSet's/ModelSerializer's, each with their access policies set. To create an object in one viewset you must give the PK of an object in the other viewset. At the moment it does not enforce the other viewset's access policy for that PK. Is does not deny access to a resource that you would otherwise not have access to through the other viewset. Is there a way to enforce the policy there?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.