[refactor] Use FilterByParent mixin in BaseEmailView #354#426
[refactor] Use FilterByParent mixin in BaseEmailView #354#426nemesifier merged 4 commits intoopenwisp:masterfrom
Conversation
63dd5f9 to
d961dc3
Compare
| def assert_parent_exists(self): | ||
| parent_queryset = self.get_parent_queryset() | ||
| if not self.request.user.is_superuser: | ||
| parent_queryset = self.get_organization_queryset(parent_queryset) |
There was a problem hiding this comment.
This doesn't look like something that was requested, why are you changing this?
There was a problem hiding this comment.
This can't be removed as it's intended to restrict non superusers so they can only see objects of their organization and not objects of other organizations. Removing this is absolutely wrong. The fact that no tests are failing for this is bad and requires opening a new issue to add at least one which would fail without this code.
Why? What's the reasoning behind this choice?
The fact that no test fails after the removal is concerning, it shows a lack of test coverage, but I am pretty sure it is not ok to remove this code.
Can you explain why you are compelled to change the internal logic of these classes instead of focusing on what the issue description is asking? I don't understand your reasoning behind these changes. Please explain so we can understand. |
openwisp_users/api/views.py
Outdated
|
|
||
|
|
||
| class BaseEmailView(ProtectedAPIMixin, GenericAPIView): | ||
| class BaseEmailView(ProtectedAPIMixin, FilterByParentOwned, GenericAPIView): |
There was a problem hiding this comment.
I think this should be:
| class BaseEmailView(ProtectedAPIMixin, FilterByParentOwned, GenericAPIView): | |
| class BaseEmailView(ProtectedAPIMixin, FilterByParentManaged, GenericAPIView): |
That allows organization managers to use the endpoint.
There was a problem hiding this comment.
Use FilterByParentManaged here please.
|
In the get_organization_queryset method,
What I have gone through:
Please provide your thoughts on this |
nemesifier
left a comment
There was a problem hiding this comment.
In the get_organization_queryset method,
def get_organization_queryset(self, qs): lookup = {self.organization_lookup: getattr(self.request.user, self._user_attr)} return qs.filter(**lookup)
self.organization_lookuphas the valueorganization__in, but in theUsermodel, there is no direct link toOrganization. It is managed byOrganizationUserto obtain many-to-many relationships between the Organization and User models. So, it addsopenwisp_useras the prefix of the organization field and makes the field nameopenwisp_user_organization. However, we are searching for the field nameorganization, which cannot be found and causes afield error.
Override what's needed in BaseEmailView to get the desired result.
What I have gone through:
- Remove this if condition
if not self.request.user.is_superuseras it was not used in the previous implementation ofBaseEmailViewassert_parent_existsmethod. All test case passes so I have gone for this.
Not the right approach, as already stated.
- I have tried changing the lookup to check for the
openwisp_user_organizationfield instead, but it causes other test failures. The models defined in /test folder likeConfig,Book,Shelfetc. have ForeignKey to the organization so test on these expecting field names asorganization.
You have to override the methods in BaseEmailView, not in the base class (which is super wrong and would cause problems to the entire set of OpenWISP modules).
| def assert_parent_exists(self): | ||
| parent_queryset = self.get_parent_queryset() | ||
| if not self.request.user.is_superuser: | ||
| parent_queryset = self.get_organization_queryset(parent_queryset) |
There was a problem hiding this comment.
This can't be removed as it's intended to restrict non superusers so they can only see objects of their organization and not objects of other organizations. Removing this is absolutely wrong. The fact that no tests are failing for this is bad and requires opening a new issue to add at least one which would fail without this code.
openwisp_users/api/views.py
Outdated
|
|
||
|
|
||
| class BaseEmailView(ProtectedAPIMixin, GenericAPIView): | ||
| class BaseEmailView(ProtectedAPIMixin, FilterByParentOwned, GenericAPIView): |
There was a problem hiding this comment.
Use FilterByParentManaged here please.
b574634 to
df1ae06
Compare
Refactored BaseEmailView to inherit from FilterByParent and for that updated get_organization_queryset to override FilterByParent's method. Fixes openwisp#354
df1ae06 to
635d776
Compare
| super().initial(*args, **kwargs) | ||
| self.assert_parent_exists() | ||
|
|
||
| def assert_parent_exists(self): |
There was a problem hiding this comment.
This method is shipped by FilterByParent so we can remove it.
| return qs_user.filter(pk=self.kwargs['pk']) | ||
| qs = User.objects.filter(pk=self.kwargs['pk']) | ||
| if self.request.user.is_superuser: | ||
| return qs |
There was a problem hiding this comment.
if the user performing the request is superuser, just return the parent without further checks (superusers can do anything).
| return qs | ||
| return self.get_organization_queryset(qs) | ||
|
|
||
| def get_organization_queryset(self, qs): |
There was a problem hiding this comment.
The goal of this method, if I am not mistaken, is to ensure that the parent object is related to one of the organizations the user performing the API request manages, otherwise the API shall return 404 because nothing is found (the query doens't return any result).
| return self.get_organization_queryset(qs) | ||
|
|
||
| def get_organization_queryset(self, qs): | ||
| orgs = self.request.user.organizations_managed |
There was a problem hiding this comment.
Therefore we use this handy method to get the list of organization IDs the user manages.
openwisp_users/api/views.py
Outdated
| orgs = self.request.user.organizations_managed | ||
| return qs.filter( | ||
| # exclude superusers | ||
| is_superuser=False, |
There was a problem hiding this comment.
We exclude superusers as before, nothing should have changed here, organization managers can't mess with superusers, that's the point.
openwisp_users/api/views.py
Outdated
| # exclude superusers | ||
| is_superuser=False, | ||
| # ensure user is member of the org | ||
| openwisp_users_organizationuser__organization_id__in=orgs |
There was a problem hiding this comment.
Here we basically ensure the parent user is member of one of the organizations managed by the user performing the API reqeust.
openwisp_users/api/views.py
Outdated
| is_superuser=False, | ||
| # ensure user is member of the org | ||
| openwisp_users_organizationuser__organization_id__in=orgs | ||
| ).distinct() |
There was a problem hiding this comment.
Not sure this is really needed anymore.
There was a problem hiding this comment.
The distinct is required. There could be a scenario where org manager manages two organizations and an end user is part of both organizations. Then, that user will appear twice in the queryset.
In [2]: org_ids = Organization.objects.values_list('id', flat=True)
In [3]: org_ids
Out[3]: (0.000) SELECT "openwisp_users_organization"."id" FROM "openwisp_users_organization" ORDER BY "openwisp_users_organization"."name" ASC LIMIT 21; args=(); alias=default
<QuerySet [UUID('57197e42-b7a9-4342-b1ef-672d7fd6ed59'), UUID('33150131-ad21-40fb-8642-365499fb01d9')]>
In [4]: User.objects.filter(openwisp_users_organizationuser__organization_id__in=org_ids)
Out[4]: (0.000) SELECT "openwisp_users_user"."password", "openwisp_users_user"."last_login", "openwisp_users_user"."is_superuser", "openwisp_users_user"."username", "openwisp_users_user"."first_name", "openwisp_users_user"."last_name", "openwisp_users_user"."is_staff", "openwisp_users_user"."is_active", "openwisp_users_user"."date_joined", "openwisp_users_user"."id", "openwisp_users_user"."email", "openwisp_users_user"."bio", "openwisp_users_user"."url", "openwisp_users_user"."company", "openwisp_users_user"."location", "openwisp_users_user"."phone_number", "openwisp_users_user"."birth_date", "openwisp_users_user"."notes", "openwisp_users_user"."language", "openwisp_users_user"."password_updated" FROM "openwisp_users_user" INNER JOIN "openwisp_users_organizationuser" ON ("openwisp_users_user"."id" = "openwisp_users_organizationuser"."user_id") WHERE "openwisp_users_organizationuser"."organization_id" IN (SELECT U0."id" FROM "openwisp_users_organization" U0) LIMIT 21; args=(); alias=default
<QuerySet [<User: orguser>, <User: orguser>]>
There was a problem hiding this comment.
Thanks for double checking! 👍
openwisp_users/api/views.py
Outdated
| # exclude superusers | ||
| is_superuser=False, | ||
| # ensure user is member of the org | ||
| openwisp_users_organizationuser__organization_id__in=orgs |
There was a problem hiding this comment.
This will probably cause issues when testing the feature with sample app. I have run
| openwisp_users_organizationuser__organization_id__in=orgs | |
| f'{app_label}_organizationuser__organization_id__in=orgs |
openwisp_users/api/views.py
Outdated
| is_superuser=False, | ||
| # ensure user is member of the org | ||
| openwisp_users_organizationuser__organization_id__in=orgs | ||
| ).distinct() |
There was a problem hiding this comment.
Thanks for double checking! 👍
openwisp_users/api/views.py
Outdated
| # exclude superusers | ||
| is_superuser=False, | ||
| # ensure user is member of the org | ||
| openwisp_users_organizationuser__organization_id__in=orgs |
Refactored BaseEmailView to inherit from FilterByParent mixin.
Checklist
Reference to Existing Issue
Closes #354 .
Description of Changes
I have removed the
assert_parent_existsmethod and inBaseEmailViewas It is present inFilterByParentand in the 'assert_parent_exists' method in FilterByParent made some changes which I am not sure to be correct but all the test cases are passing now.I have a few questions as well
In between
FilterByParent,FilterByParentMembership,FilterByParentManaged, andFilterByParentOwnedwhich class to inherit I have gone forFilterByParentOwned.Is this okay to remove this in the method
assert_parent_existsin classFilterByParent? All test cases are passing after the removal as well.Removed the above code because it is causing issues, in the
get_organization_querysetmethod theself.organization_lookupfrom theOrgLookupclass hasorganization__ininside it but the field in theEmailAddressmodel isopenwisp_user_orginizationnotorganizationcausingFieldErrorthat's why decided to remove this.OrgLookupclass to addorganization_lookupcorrectly or removing the code is fine?Please give your thoughts on this!