Coverage for cc_modules/cc_taskfactory.py: 32%

44 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 23:14 +0000

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/cc_taskfactory.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

10 

11 This file is part of CamCOPS. 

12 

13 CamCOPS is free software: you can redistribute it and/or modify 

14 it under the terms of the GNU General Public License as published by 

15 the Free Software Foundation, either version 3 of the License, or 

16 (at your option) any later version. 

17 

18 CamCOPS is distributed in the hope that it will be useful, 

19 but WITHOUT ANY WARRANTY; without even the implied warranty of 

20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

21 GNU General Public License for more details. 

22 

23 You should have received a copy of the GNU General Public License 

24 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

25 

26=============================================================================== 

27 

28**Functions to fetch tasks from the database.** 

29 

30""" 

31 

32import logging 

33from typing import Optional, Type, TYPE_CHECKING, Union 

34 

35from cardinal_pythonlib.logs import BraceStyleAdapter 

36import pyramid.httpexceptions as exc 

37from sqlalchemy.orm import Query, Session as SqlASession 

38 

39from camcops_server.cc_modules.cc_task import ( 

40 tablename_to_task_class_dict, 

41 Task, 

42) 

43from camcops_server.cc_modules.cc_taskindex import TaskIndexEntry 

44 

45if TYPE_CHECKING: 

46 from camcops_server.cc_modules.cc_request import CamcopsRequest 

47 

48log = BraceStyleAdapter(logging.getLogger(__name__)) 

49 

50 

51# ============================================================================= 

52# Task query helpers 

53# ============================================================================= 

54 

55 

56def task_query_restricted_to_permitted_users( 

57 req: "CamcopsRequest", 

58 q: Query, 

59 cls: Union[Type[Task], Type[TaskIndexEntry]], 

60 as_dump: bool, 

61) -> Optional[Query]: 

62 """ 

63 Restricts an SQLAlchemy ORM query to permitted users, for a given 

64 task class. THIS IS A KEY SECURITY FUNCTION. 

65 

66 Args: 

67 req: 

68 the :class:`camcops_server.cc_modules.cc_request.CamcopsRequest` 

69 q: 

70 the SQLAlchemy ORM query 

71 cls: 

72 the class of the task type, or the 

73 :class:`camcops_server.cc_modules.cc_taskindex.TaskIndexEntry` 

74 class 

75 as_dump: 

76 use the "dump" permissions rather than the "view" permissions? 

77 

78 Returns: 

79 a filtered query (or the original query, if no filtering was required) 

80 

81 """ 

82 user = req.user 

83 

84 if user.superuser: 

85 return q # anything goes 

86 

87 # Implement group security. Simple: 

88 if as_dump: 

89 group_ids = user.ids_of_groups_user_may_dump 

90 else: 

91 group_ids = user.ids_of_groups_user_may_see 

92 

93 if not group_ids: 

94 return None 

95 

96 if cls is TaskIndexEntry: 

97 # noinspection PyUnresolvedReferences 

98 q = q.filter(cls.group_id.in_(group_ids)) 

99 else: # a kind of Task 

100 q = q.filter(cls._group_id.in_(group_ids)) 

101 

102 return q 

103 

104 

105# ============================================================================= 

106# Make a single task given its base table name and server PK 

107# ============================================================================= 

108 

109 

110def task_factory( 

111 req: "CamcopsRequest", basetable: str, serverpk: int 

112) -> Optional[Task]: 

113 """ 

114 Load a task from the database and return it. 

115 Filters to tasks permitted to the current user. 

116 

117 Args: 

118 req: the :class:`camcops_server.cc_modules.cc_request.CamcopsRequest` 

119 basetable: name of the task's base table 

120 serverpk: server PK of the task 

121 

122 Returns: 

123 the task, or ``None`` if the PK doesn't exist 

124 

125 Raises: 

126 :exc:`HTTPBadRequest` if the table doesn't exist 

127 

128 """ 

129 d = tablename_to_task_class_dict() 

130 try: 

131 cls = d[basetable] # may raise KeyError 

132 except KeyError: 

133 raise exc.HTTPBadRequest(f"No such task table: {basetable!r}") 

134 dbsession = req.dbsession 

135 # noinspection PyProtectedMember 

136 q = dbsession.query(cls).filter(cls._pk == serverpk) 

137 q = task_query_restricted_to_permitted_users(req, q, cls, as_dump=False) 

138 return q.first() 

139 

140 

141def task_factory_no_security_checks( 

142 dbsession: SqlASession, basetable: str, serverpk: int 

143) -> Optional[Task]: 

144 """ 

145 Load a task from the database and return it. 

146 Filters to tasks permitted to the current user. 

147 

148 Args: 

149 dbsession: a :class:`sqlalchemy.orm.session.Session` 

150 basetable: name of the task's base table 

151 serverpk: server PK of the task 

152 

153 Returns: 

154 the task, or ``None`` if the PK doesn't exist 

155 

156 Raises: 

157 :exc:`KeyError` if the table doesn't exist 

158 """ 

159 d = tablename_to_task_class_dict() 

160 cls = d[basetable] # may raise KeyError 

161 # noinspection PyProtectedMember 

162 q = dbsession.query(cls).filter(cls._pk == serverpk) 

163 return q.first() 

164 

165 

166# ============================================================================= 

167# Make a single task given its base table name and server PK 

168# ============================================================================= 

169 

170 

171def task_factory_clientkeys_no_security_checks( 

172 dbsession: SqlASession, 

173 basetable: str, 

174 client_id: int, 

175 device_id: int, 

176 era: str, 

177) -> Optional[Task]: 

178 """ 

179 Load a task from the database and return it. 

180 Filters to tasks permitted to the current user. 

181 

182 Args: 

183 dbsession: a :class:`sqlalchemy.orm.session.Session` 

184 basetable: name of the task's base table 

185 client_id: task's ``_id`` value 

186 device_id: task's ``_device_id`` value 

187 era: task's ``_era`` value 

188 

189 Returns: 

190 the task, or ``None`` if it doesn't exist 

191 

192 Raises: 

193 :exc:`KeyError` if the table doesn't exist 

194 """ 

195 d = tablename_to_task_class_dict() 

196 cls = d[basetable] # may raise KeyError 

197 # noinspection PyProtectedMember 

198 q = ( 

199 dbsession.query(cls) 

200 .filter(cls.id == client_id) 

201 .filter(cls._device_id == device_id) 

202 .filter(cls._era == era) 

203 ) 

204 return q.first()