Skip to content
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

Initial 'security' changes - Target PATCH now "locked down" #612

Merged
merged 39 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
205e59f
fix: Adds extra log to Targets view
Jun 21, 2024
fcd43c8
fix: More target logging
Jun 21, 2024
65eb128
fix: safe query set now derived from ModelViewSet
Jun 21, 2024
b781a50
feat: Experiment with IsProposalMember class
Jun 21, 2024
63f752c
fix: Another permission tweak
Jun 21, 2024
36a2992
fix: Another permission tweak
Jun 21, 2024
a395f01
fix: Typo
Jun 21, 2024
1069392
fix: Fixed silly typo
Jun 21, 2024
c6f89cd
fix: Display view filter_permissions
Jun 21, 2024
390d6ae
fix: Experimental new has_permission
Jun 22, 2024
7052867
fix: Add user_is_member_of_any_given_proposals to security
Jun 22, 2024
c17fac3
fix: Change to filter class name
Jun 22, 2024
effeb12
fix: Better has_object_permission
Jun 22, 2024
bec00ac
feat: Leaner has_object_permission
Jun 22, 2024
c769fe3
docs: Minor doc tweak
Jun 22, 2024
4fb81d2
fix: Experiment with SafeQuerySet
Jun 22, 2024
b2d0f11
fix: Experiment with ModelViewSet in ISPYB
Jun 22, 2024
7b1df7d
fix: Back to read-only viewset (and reduced log)
Jun 22, 2024
3965563
fix: User must be authenticated
Jun 22, 2024
7cf1869
docs: Doc tweak
Jun 23, 2024
9b2fc8e
feat: Experiment with filter class for Target view
Jun 24, 2024
f51be1e
fix: Attempt to fix build errors
Jun 24, 2024
81da348
fix: Switch to filter_class
Jun 24, 2024
2154691
fix: Attempt to fix filter logging
Jun 24, 2024
0329202
fix: Another tweak of filters
Jun 24, 2024
82787de
feat: Fix filerset typo
Jun 24, 2024
b9c49f3
fix: Back to built-in (genric) views
Jun 24, 2024
9909721
feat: Attempt to fix has no attribute 'get_extra_actions'
Jun 24, 2024
19a4920
fix: Silly typo
Jun 24, 2024
9f53091
fix: Back to ispybsafequeryset
Jun 24, 2024
f33dc60
fix: Restore queryset
Jun 24, 2024
0217118
fix: Experiment with mixins
Jun 24, 2024
4528006
docs: Doc tweaks
Jun 24, 2024
d9ad42b
docs: Doc tweak
Jun 24, 2024
f1ef712
fix: Switch to update() from patch()
Jun 24, 2024
2f93866
fix: Back to patch()
Jun 24, 2024
da1a4bb
refactor: Minor refactor
Jun 24, 2024
23fcb97
feat: Better permissions class for proposals
Jun 25, 2024
ba81385
Align 1247 with latest staging (#611)
alanbchristie Jun 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions api/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@

@cache
class CachedContent:
"""A static class managing caches proposals/visits for each user.
"""
A static class managing caches proposals/visits for each user.
Proposals should be collected when has_expired() returns True.
Content can be written (when the cache for the user has expired)
and read using the set/get methods.
Expand Down Expand Up @@ -185,9 +186,22 @@ def ping_configured_connector() -> bool:


class ISpyBSafeQuerySet(viewsets.ReadOnlyModelViewSet):
"""
This ISpyBSafeQuerySet, which inherits from the DRF viewsets.ReadOnlyModelViewSet,
is used for all views that need to yield (filter) view objects based on a
user's proposal membership. This requires the view to define the property
"filter_permissions" to enable this class to navigate to the view object's Project
(proposal/visit).

As the ISpyBSafeQuerySet is based on a ReadOnlyModelViewSet, which only provides
implementations for list() and retrieve() methods, the user will need to provide
"mixins" for any additional methods the view needs to support (PATCH, PUT, DELETE).
"""

def get_queryset(self):
"""
Optionally restricts the returned purchases to a given proposals
Restricts the returned records to those that belong to proposals
the user has access to. Without a user only 'open' proposals are returned.
"""
# The list of proposals this user can have
proposal_list = self.get_proposals_for_user(self.request.user)
Expand All @@ -199,7 +213,7 @@ def get_queryset(self):

# Must have a foreign key to a Project for this filter to work.
# get_q_filter() returns a Q expression for filtering
q_filter = self.get_q_filter(proposal_list)
q_filter = self._get_q_filter(proposal_list)
return self.queryset.filter(q_filter).distinct()

def _get_open_proposals(self):
Expand Down Expand Up @@ -267,8 +281,8 @@ def _get_proposals_for_user_from_ispyb(self, user):
return cached_prop_ids

def _get_proposals_from_connector(self, user, conn):
"""Updates the USER_LIST_DICT with the results of a query
and marks it as populated.
"""
Updates the user's proposal cache with the results of a query
"""
assert user
assert conn
Expand Down Expand Up @@ -327,8 +341,19 @@ def _get_proposals_from_connector(self, user, conn):
)
CachedContent.set_content(user.username, prop_id_set)

def user_is_member_of_any_given_proposals(self, user, proposals):
"""
Returns true if the user has access to any proposal in the given
proposals list.Only one needs to match for permission to be granted.
We 'restrict_to_membership' to only consider proposals the user
has explicit membership.
"""
user_proposals = self.get_proposals_for_user(user, restrict_to_membership=True)
return any(proposal in user_proposals for proposal in proposals)

def get_proposals_for_user(self, user, restrict_to_membership=False):
"""Returns a list of proposals that the user has access to.
"""
Returns a list of proposals that the user has access to.

If 'restrict_to_membership' is set only those proposals/visits where the user
is a member of the visit will be returned. Otherwise the 'public'
Expand Down Expand Up @@ -367,7 +392,7 @@ def get_proposals_for_user(self, user, restrict_to_membership=False):
# Return the set() as a list()
return list(proposals)

def get_q_filter(self, proposal_list):
def _get_q_filter(self, proposal_list):
"""Returns a Q expression representing a (potentially complex) table filter."""
if self.filter_permissions:
# Q-filter is based on the filter_permissions string
Expand Down
Loading