-
Notifications
You must be signed in to change notification settings - Fork 29
Rework org perms again #5657
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework org perms again #5657
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,54 +22,45 @@ class OrgPermsMixin: | |
def derive_org(self): | ||
return self.request.org | ||
|
||
def _check_org_perm(self, permission: str) -> tuple[bool, bool]: | ||
def has_org_perm(self, permission: str) -> bool: | ||
""" | ||
Checks if the current user has the given permission for the current org and whether they have it by servicing. | ||
Figures out if the current user has the given permission. | ||
""" | ||
|
||
org = self.derive_org() | ||
user = self.request.user | ||
|
||
# can't have an org perm without an org | ||
if not org: | ||
return False, False | ||
return False | ||
|
||
if user.is_anonymous: | ||
return False, False | ||
|
||
has_perm = user.has_org_perm(org, permission) | ||
|
||
if not has_perm and user.is_staff: | ||
return True, True | ||
|
||
return has_perm, False | ||
return False | ||
|
||
def has_org_perm(self, permission: str) -> bool: | ||
has_perm, by_servicing = self._check_org_perm(permission) | ||
return has_perm | ||
return user.is_staff or user.has_org_perm(org, permission) | ||
|
||
def has_permission(self, request, *args, **kwargs) -> bool: | ||
""" | ||
Figures out if the current user has permissions for this view. | ||
Figures out if the current user has the required permission for this view. | ||
""" | ||
|
||
self.kwargs = kwargs | ||
self.args = args | ||
self.request = request | ||
|
||
has_perm, by_servicing = self._check_org_perm(self.permission) | ||
|
||
# by default staff are only allowed to GET views when servicing | ||
if self.readonly_servicing and by_servicing and request.method != "GET": | ||
return False | ||
|
||
return has_perm | ||
return self.has_org_perm(self.permission) | ||
|
||
def dispatch(self, request, *args, **kwargs): | ||
# non admin authenticated users without orgs get the org chooser | ||
org = self.derive_org() | ||
user = self.request.user | ||
if user.is_authenticated and not user.is_staff: | ||
if not self.derive_org(): | ||
|
||
if org: | ||
# when servicing, non-superuser staff can only GET | ||
is_servicing = request.user.is_staff and not org.users.filter(id=request.user.id).exists() | ||
if is_servicing and not request.user.is_superuser and request.method != "GET": | ||
return HttpResponseForbidden() | ||
else: | ||
if user.is_authenticated and not user.is_staff: | ||
return HttpResponseRedirect(reverse("orgs.org_choose")) | ||
|
||
return super().dispatch(request, *args, **kwargs) | ||
|
@@ -86,20 +77,30 @@ def get_object_org(self): | |
def has_permission(self, request, *args, **kwargs) -> bool: | ||
has_perm = super().has_permission(request, *args, **kwargs) | ||
|
||
# allow staff to GET any object because they'll be redirected on org mismatch | ||
if self.request.user.is_staff and self.request.method == "GET": | ||
# even without a current org, staff can switch to the object's org | ||
if request.user.is_staff: | ||
return True | ||
|
||
return has_perm and self.request.org == self.get_object_org() | ||
if not has_perm: | ||
return False | ||
|
||
obj_org = self.get_object_org() | ||
is_this_org = self.request.org == obj_org | ||
|
||
return has_perm and (is_this_org or obj_org.get_users().filter(id=request.user.id).exists()) | ||
|
||
def pre_process(self, request, *args, **kwargs): | ||
org = self.get_object_org() | ||
|
||
# staff users are redirected to service page if org doesn't match | ||
if request.user.is_staff and self.request.org != org: | ||
return HttpResponseRedirect( | ||
f"{reverse('staff.org_service')}?next={quote_plus(request.path)}&other_org={org.id}" | ||
) | ||
if self.request.org != org: | ||
if request.user.is_staff: | ||
# staff users are redirected to service page if org doesn't match | ||
return HttpResponseRedirect( | ||
f"{reverse('staff.org_service')}?next={quote_plus(request.path)}&other_org={org.id}" | ||
) | ||
else: | ||
# TODO implement view to let regular users switch orgs as well | ||
return HttpResponseRedirect(reverse("orgs.org_choose")) | ||
Comment on lines
+102
to
+103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can allow the choose view to accept a get param and use that for initial value of the form and the use can confirm to switch by a button(submitting the form) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could but I think it's ok to have a different view for switching to a specific org |
||
|
||
return super().pre_process(request, *args, **kwargs) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,7 +48,7 @@ def test_has_permission(self): | |
self.assertEqual(200, self.client.get(create_url).status_code) | ||
|
||
# staff still can't POST | ||
self.assertLoginRedirect(self.client.post(create_url, {"name": "Sales"})) | ||
self.assertEqual(403, self.client.post(create_url, {"name": "Sales"}).status_code) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ericnewcomer ok so this is changing again.. but I think this is safer. You POST as staff, the request just fails. No redirecting to login and then maybe back and wondering what happened with the original form submission. |
||
|
||
# but superuser can | ||
self.customer_support.is_superuser = True | ||
|
@@ -64,9 +64,10 @@ def test_has_permission(self): | |
self.assertEqual(200, self.client.get(create_url).status_code) | ||
self.assertRedirect(self.client.post(create_url, {"name": "Support"}), "hide") | ||
|
||
def test_org_obj_perms_mixin(self): | ||
def test_obj_perms_mixin(self): | ||
contact1 = self.create_contact("Bob", phone="+18001234567", org=self.org) | ||
contact2 = self.create_contact("Zob", phone="+18001234567", org=self.org2) | ||
self.org2.add_user(self.admin, OrgRole.ADMINISTRATOR) | ||
|
||
contact1_url = reverse("contacts.contact_update", args=[contact1.id]) | ||
contact2_url = reverse("contacts.contact_update", args=[contact2.id]) | ||
|
@@ -80,11 +81,16 @@ def test_org_obj_perms_mixin(self): | |
self.assertLoginRedirect(self.client.get(contact1_url)) | ||
self.assertLoginRedirect(self.client.get(contact2_url)) | ||
|
||
# editor role does have access tho.. when the URL is for a group in their org | ||
# editor does have access tho.. when the URL is for a contact in their org | ||
self.login(self.editor) | ||
self.assertEqual(200, self.client.get(contact1_url).status_code) | ||
self.assertLoginRedirect(self.client.get(contact2_url)) | ||
|
||
# admin belongs to both orgs | ||
self.login(self.admin, choose_org=self.org) | ||
self.assertEqual(200, self.client.get(contact1_url).status_code) | ||
self.assertRedirect(self.client.get(contact2_url), reverse("orgs.org_choose")) | ||
|
||
# staff can't access without org | ||
self.login(self.customer_support) | ||
self.assertRedirect(self.client.get(contact1_url), "/staff/org/service/") | ||
|
@@ -94,7 +100,7 @@ def test_org_obj_perms_mixin(self): | |
self.assertRedirect(self.client.get(contact2_url), "/staff/org/service/") # wrong org | ||
|
||
# staff still can't POST | ||
self.assertLoginRedirect(self.client.post(contact1_url, {"name": "Bob"})) | ||
self.assertEqual(403, self.client.post(contact1_url, {"name": "Bob"}).status_code) | ||
self.assertRedirect(self.client.get(contact2_url), "/staff/org/service/") | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
really gotta figure out a consistent behavior for when you hit an object specific URL for an object you don't have access to (but might by switching orgs). Here we're changing to a 404 from a login redirect.