-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8bff2fa
commit 88d925f
Showing
15 changed files
with
297 additions
and
384 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
# django-woah | ||
A package intended to aid developers in implementing authorization for Django apps. | ||
|
||
A package intended to aid developers in implementing authorization for Django apps. | ||
*This project was developed at [Presslabs](https://www.presslabs.com/).* | ||
|
||
## Installation | ||
`pip install django-woah` | ||
|
@@ -190,15 +190,16 @@ To see more code in action you can check the [examples](https://github.com/press | |
- Although the library hasn't reached version 1.0 yet, it is soon going to be used in production at Presslabs, with most, if not all of it's functionality tested. | ||
- This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). That means until version 1.0, breaking changes are to be expected from one version to another, although they will be documented in the [changelog](CHANGELOG.MD). | ||
- There is a good chance for pre-1.0 versions to be maintained for a while, in terms of compatibility with newer Django and Python versions, as well as critical bugfixes. You might have to provide a pull request yourself though, but we'll, at the least, review it and hopefully ship it in a maintenance release. | ||
- The abstractions around how Conditions are composed and relate to AuthorizationSchemes/Solver could've been more inspired (see [Shortcomings](#shortcomings)). Therefore, a major rework could happen before the 1.0 release, but chances are it will take a while longer to materialize, as the current API is *usable* enough. | ||
- The abstractions around how Conditions are composed and relate to AuthorizationSchemes/Solver could've been more inspired (see [Shortcomings and Limitations](#shortcomings-and-limitations)). Therefore, a major rework could happen before the 1.0 release, but chances are it will take a while longer to materialize, as the current API is *usable* enough. | ||
|
||
|
||
## Shortcomings | ||
## Shortcomings and Limitations | ||
|
||
- The models and logic currently work with a single owner type relation, pointing to the Django `AUTH_USER_MODEL`. This implies that your Organizations must share the same model with your Users (which we believe simplifies things for most cases). It should be possible to work around this limitation, but out of the box everything is set up to work this way. | ||
- It's hard (and not performant) to interrogate who all the users with privileges for a resource are. | ||
- It's not possible to define and store new permissions/roles in the DB. | ||
- It's cumbersome to verify if a subset of *Conditions* is being met. And when enforcing authorization, it's kind of impossible to reveal the conditions that have not been met. | ||
- Verifying authorization for already prefetched resources, in cases where conditions can be satisfied without the need to fetch AssignedPerms, or the AssignedPerms have been prefetched as well, could be more performant. The best way of doing it now is filtering for which of them is satisfy authorization, as if they weren't prefetched to begin with. | ||
- Verifying authorization for already prefetched resources, in cases where conditions can be satisfied without the need to fetch AssignedPerms, or the AssignedPerms have been prefetched as well, could be more performant. The best way of doing it now is filtering which of them the actor is authorized for, as if they weren't prefetched to begin with. | ||
- For some cases, prefetching AssignedPerms could be avoided, and the whole authorization interrogation could be done with a single query... but not with how the abstraction is currently built. That single query would consist of more DB joins, so it's hard to tell if a potential performance increase is left on the table or not, without actual benchmarks. | ||
- Memberships could be made more optional in the whole design, but it's not clear if that's of any importance right now. | ||
- Some "meta" indirect privileges are hard (or even impossible) to implement, especially in a performant manner. For example: giving privileges that other users possess, based on a relation between the actor and the respective users, if say they are part of the same UserGroups. | ||
|
@@ -213,7 +214,8 @@ If these are dealbreakers for you or you are simply looking for something else, | |
- For security related issues (think exploitable bugs), contact us at `[email protected]`. | ||
- For other type of bugs, use the [issue tracker](https://github.com/presslabs/django-woah/issues). | ||
- If you have questions, or want to talk about missing functionality, open a [discussion](https://github.com/presslabs/django-woah/discussions). | ||
- You may send a [pull request](https://github.com/presslabs/django-woah/pulls) for bugfixing, if you think you've got it right. For anything else, if the implementation details have not already been decided, it's better to start a [discussion](https://github.com/presslabs/django-woah/discussions) first. | ||
- You may send a [pull request](https://github.com/presslabs/django-woah/pulls) for bugfixing, if you think you've got it right. For anything else, if the implementation details have not already been decided, it's better to start a [discussion](https://github.com/presslabs/django-woah/discussions) first. | ||
Do take note that we're looking to implement a CLA for code contributions. | ||
- For anything else, just use common sense and it will probably be fine. | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
# Copyright 2024 Pressinfra SRL | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from django.db import models | ||
|
||
|
||
class AutoCleanModel(models.Model): | ||
class Meta: | ||
abstract = True | ||
|
||
def _init_states(self): | ||
self.initial_state = self.current_state | ||
|
||
self.cleaned_state = {} if not self.pk else self.initial_state.copy() | ||
self.saved_state = {} if not self.pk else self.initial_state.copy() | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self._init_states() | ||
|
||
@property | ||
def current_state(self): | ||
return { | ||
field.name: self.__dict__[field.attname] | ||
for field in self._meta.fields | ||
if field.attname in self.__dict__ | ||
} | ||
|
||
@staticmethod | ||
def _states_diff(state, other_state): | ||
return {key: value for key, value in other_state.items() if value != state[key]} | ||
|
||
def get_dirty_fields(self): | ||
return self._states_diff(self.current_state, self.cleaned_state) | ||
|
||
def get_unsaved_fields(self): | ||
if not self.saved_state: | ||
return list(self.current_state.keys()) | ||
|
||
return list(self._states_diff(self.current_state, self.saved_state).keys()) | ||
|
||
@property | ||
def is_cleaned(self): | ||
if not getattr(self, ".cleaned", False): | ||
return False | ||
|
||
return not self.get_dirty_fields() | ||
|
||
@is_cleaned.setter | ||
def is_cleaned(self, value): | ||
if value: | ||
self.cleaned_state = self.current_state.copy() | ||
|
||
setattr(self, ".cleaned", value) | ||
|
||
def save(self, *args, **kwargs): | ||
if not self.is_cleaned: | ||
self.full_clean() | ||
|
||
super().save(*args, **kwargs) | ||
|
||
self.initial_state = self.current_state.copy() | ||
if kwargs.get("update_fields") is None: | ||
self.saved_state = self.current_state.copy() | ||
else: | ||
for field in kwargs["update_fields"]: | ||
if field not in self.current_state: | ||
continue | ||
|
||
self.saved_state[field] = self.current_state[field] | ||
|
||
def refresh_from_db(self, *args, **kwargs): | ||
super().refresh_from_db(*args, **kwargs) | ||
|
||
self._init_states() | ||
|
||
def full_clean(self, *args, **kwargs): | ||
if self.is_cleaned: | ||
return | ||
|
||
super().full_clean(*args, **kwargs) | ||
|
||
self.is_cleaned = True |
Oops, something went wrong.