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

1''' 

2Validate that a given user can see various objects 

3or perform various tasks 

4''' 

5 

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) 

14 

15 

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 

27 

28 @param multiproject - some actions cover mutliple projects, thus it's not possible 

29 to pass the Project into this function 

30 ''' 

31 

32 if action not in perms.ALL_PERMISSIONS: 

33 raise ValueError('Action %s is not associated with a valid permission' % action) 

34 

35 e = ValidationErrors(action) 

36 

37 if deny_restricted and user.is_restricted: 

38 e.auth_failure('Action not permitted for Restricted (Domain Expert) users') 

39 

40 if not user.has_permission(action): 

41 e.auth_failure('User lacks permission for %s' % action) 

42 

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)) 

51 

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) 

55 

56 if user.is_restricted and user not in project.restricted_users: 

57 e.auth_failure('User not granted permissions to this project') 

58 

59 if action not in PROJECT_STATUS_ACTIONS[project.status]: 

60 e.invalid_project_status(project.status) 

61 

62 project_rules = ProjectRules(project, user, e) 

63 project_rules.check(action) 

64 

65 if action in QUESTIONNAIRE_ACTIONS: 

66 

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}') 

74 

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})') 

78 

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) 

85 

86 if action in ADMIN_ACTIONS and user.organisation is not target_org: 

87 

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') 

90 

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) 

100 

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) 

108 

109 if e.has_errors: 

110 raise AuthorizationFailure(errors=e) 

111 

112 

113class ProjectRules(object): 

114 

115 """Business logic applying to specific project settings""" 

116 

117 def __init__(self, project, user, errors): 

118 self.project = project 

119 self.user = user 

120 self.errors = errors 

121 

122 def check(self, action): 

123 actionCheck = getattr(self, action, None) 

124 if actionCheck is not None: 

125 actionCheck() 

126 

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') 

130 

131 if self.project.lock_issues: 

132 self.errors.auth_failure( 

133 'Cannot view answers while the Project is set to Lock Responses')