Coverage for rfpy/api/validate.py: 100%
72 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-31 16:00 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-31 16:00 +0000
1'''
2Validate that a given user can see various objects
3or perform various tasks
4'''
6from rfpy.auth import AuthorizationFailure, ValidationErrors
7from rfpy.auth import perms
8from rfpy.model.project import Project
9from rfpy.model.issue import Issue
10from rfpy.model.humans import Organisation, OrganisationType, User
11from rfpy.auth.actions import (PROJECT_ACTIONS, QUESTIONNAIRE_ACTIONS,
12 ADMIN_ACTIONS, ISSUE_ACTIONS, ISSUE_STATUS_ACTIONS,
13 PROJECT_STATUS_ACTIONS)
16def check(user: User,
17 action: str,
18 project: Project = None,
19 issue: Issue = None,
20 multiproject: bool = False,
21 section_id: int = None,
22 target_user: User = None,
23 target_org: Organisation = None,
24 deny_restricted: bool = True):
25 '''
26 Centralised statement of auth business logic, all API calls should invoke this
28 @param multiproject - some actions cover mutliple projects, thus it's not possible
29 to pass the Project into this function
30 '''
32 if action not in perms.ALL_PERMISSIONS:
33 raise ValueError('Action %s is not associated with a valid permission' % action)
35 e = ValidationErrors(action)
37 if deny_restricted and user.is_restricted:
38 e.auth_failure('Action not permitted for Restricted (Domain Expert) users')
40 if not user.has_permission(action):
41 e.auth_failure('User lacks permission for %s' % action)
43 if action in PROJECT_ACTIONS and not multiproject:
44 if project is None:
45 raise ValueError('project must be provided if checking project actions')
46 if user.org_id != project.org_id:
47 # Participant, not owner org checks
48 if user.organisation not in project.participants:
49 e.auth_failure('%s is not a participant in project %s' %
50 (user.organisation, project.title))
52 if action not in project.participant_role_permissions(user):
53 args = (user.organisation, action, project)
54 e.auth_failure('Participant %s lacks permission to %s in project %s' % args)
56 if user.is_restricted and user not in project.restricted_users:
57 e.auth_failure('User not granted permissions to this project')
59 if action not in PROJECT_STATUS_ACTIONS[project.status]:
60 e.invalid_project_status(project.status)
62 project_rules = ProjectRules(project, user, e)
63 project_rules.check(action)
65 if action in QUESTIONNAIRE_ACTIONS:
67 if user.is_restricted:
68 if section_id is None:
69 raise ValueError('section_id must be provided to validate() for ' +
70 'QUESTIONNAIRE ACTION with a Restricted User')
71 else:
72 if not user.can_view_section_id(section_id):
73 e.auth_failure(f'User has not been granted permission to section {section_id}')
75 if section_id is not None and not project.contains_section_id(section_id):
76 e.auth_failure(f'Section ID {section_id} does not belong to '
77 + f'project {project.id} ({project.title})')
79 if action in ISSUE_ACTIONS:
80 if issue is None:
81 if not (action in ISSUE_STATUS_ACTIONS['__new__'] | PROJECT_ACTIONS):
82 e.auth_failure(f"Action '{action}' not permitted for a new Issue ")
83 elif action not in ISSUE_STATUS_ACTIONS[issue.status]:
84 e.invalid_issue_status(issue.status)
86 if action in ADMIN_ACTIONS and user.organisation is not target_org:
88 if target_org is None and target_user is None:
89 raise ValueError('Either target_org or target_user must be set for admin validation')
91 if not user.organisation.is_consultant:
92 e.auth_failure('Only consultants can perform operations on users in a different org')
93 else:
94 if target_org is None:
95 target_org = target_user.organisation
96 elif target_user is not None and target_user.organisation is not target_org:
97 m = (f"Target org '{target_org.id}' must be the same as that of user: "
98 f"{target_user.id}, ({target_user.org_id})")
99 raise ValueError(m)
101 if (
102 target_org is not user.organisation
103 and target_org.type is not OrganisationType.RESPONDENT
104 and target_org not in user.organisation.clients
105 ):
106 m = (f"Organisation {target_org.id} is not a client of consultant {user.org_id}")
107 e.auth_failure(m)
109 if e.has_errors:
110 raise AuthorizationFailure(errors=e)
113class ProjectRules(object):
115 """Business logic applying to specific project settings"""
117 def __init__(self, project, user, errors):
118 self.project = project
119 self.user = user
120 self.errors = errors
122 def check(self, action):
123 actionCheck = getattr(self, action, None)
124 if actionCheck is not None:
125 actionCheck()
127 def viewAnswers(self):
128 if self.project.hide_responses and not self.project.deadline_passed:
129 self.errors.auth_failure('Cannot view answers until the after the Project deadline')
131 if self.project.lock_issues:
132 self.errors.auth_failure(
133 'Cannot view answers while the Project is set to Lock Responses')