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
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/cc_modules/cc_taskfactory.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
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.
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.
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/>.
26===============================================================================
28**Functions to fetch tasks from the database.**
30"""
32import logging
33from typing import Optional, Type, TYPE_CHECKING, Union
35from cardinal_pythonlib.logs import BraceStyleAdapter
36import pyramid.httpexceptions as exc
37from sqlalchemy.orm import Query, Session as SqlASession
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
45if TYPE_CHECKING:
46 from camcops_server.cc_modules.cc_request import CamcopsRequest
48log = BraceStyleAdapter(logging.getLogger(__name__))
51# =============================================================================
52# Task query helpers
53# =============================================================================
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.
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?
78 Returns:
79 a filtered query (or the original query, if no filtering was required)
81 """
82 user = req.user
84 if user.superuser:
85 return q # anything goes
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
93 if not group_ids:
94 return None
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))
102 return q
105# =============================================================================
106# Make a single task given its base table name and server PK
107# =============================================================================
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.
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
122 Returns:
123 the task, or ``None`` if the PK doesn't exist
125 Raises:
126 :exc:`HTTPBadRequest` if the table doesn't exist
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()
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.
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
153 Returns:
154 the task, or ``None`` if the PK doesn't exist
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()
166# =============================================================================
167# Make a single task given its base table name and server PK
168# =============================================================================
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.
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
189 Returns:
190 the task, or ``None`` if it doesn't exist
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()