Coverage for cc_modules/cc_audit.py: 60%
40 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_audit.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**Auditing.**
30The Big Brother part.
32"""
34from typing import TYPE_CHECKING
36from sqlalchemy.orm import relationship
37from sqlalchemy.sql.schema import Column, ForeignKey
38from sqlalchemy.sql.sqltypes import DateTime, Integer, UnicodeText
40from camcops_server.cc_modules.cc_sqla_coltypes import (
41 AuditSourceColType,
42 IPAddressColType,
43 TableNameColType,
44)
45from camcops_server.cc_modules.cc_sqlalchemy import Base
47if TYPE_CHECKING:
48 from camcops_server.cc_modules.cc_request import CamcopsRequest
51MAX_AUDIT_STRING_LENGTH = 65000
54# =============================================================================
55# AuditEntry
56# =============================================================================
59class AuditEntry(Base):
60 """
61 An entry in the audit table.
62 """
64 __tablename__ = "_security_audit"
66 id = Column(
67 "id",
68 Integer,
69 primary_key=True,
70 autoincrement=True,
71 index=True,
72 comment="Arbitrary primary key",
73 )
74 when_access_utc = Column(
75 "when_access_utc",
76 DateTime,
77 nullable=False,
78 index=True,
79 comment="Date/time of access (UTC)",
80 )
81 source = Column(
82 "source",
83 AuditSourceColType,
84 nullable=False,
85 comment="Source (e.g. tablet, webviewer)",
86 )
87 remote_addr = Column(
88 "remote_addr",
89 IPAddressColType,
90 comment="IP address of the remote computer",
91 )
92 user_id = Column(
93 "user_id",
94 Integer,
95 ForeignKey("_security_users.id"),
96 comment="ID of user, where applicable",
97 )
98 user = relationship("User")
99 device_id = Column(
100 "device_id",
101 Integer,
102 ForeignKey("_security_devices.id"),
103 comment="Device ID, where applicable",
104 )
105 device = relationship("Device")
106 table_name = Column(
107 "table_name",
108 TableNameColType,
109 comment="Table involved, where applicable",
110 )
111 server_pk = Column(
112 "server_pk", Integer, comment="Server PK (table._pk), where applicable"
113 )
114 patient_server_pk = Column(
115 "patient_server_pk",
116 Integer,
117 comment="Server PK of the patient (patient._pk) concerned, or "
118 "NULL if not applicable",
119 )
120 details = Column(
121 "details", UnicodeText, comment="Details of the access"
122 ) # in practice, has 65,535 character limit and isn't Unicode.
123 # See MAX_AUDIT_STRING_LENGTH above.
126# =============================================================================
127# Audit function
128# =============================================================================
131def audit(
132 req: "CamcopsRequest",
133 details: str,
134 patient_server_pk: int = None,
135 table: str = None,
136 server_pk: int = None,
137 device_id: int = None,
138 remote_addr: str = None,
139 user_id: int = None,
140 from_console: bool = False,
141 from_dbclient: bool = False,
142) -> None:
143 """
144 Write an entry to the audit log.
145 """
146 dbsession = req.dbsession
147 if not remote_addr:
148 remote_addr = req.remote_addr if req else None
149 if user_id is None:
150 user_id = req.user_id
151 if from_console:
152 source = "console"
153 elif from_dbclient:
154 source = "tablet"
155 else:
156 source = "webviewer"
157 now = req.now_utc
158 if details and len(details) > MAX_AUDIT_STRING_LENGTH:
159 details = details[:MAX_AUDIT_STRING_LENGTH]
160 # noinspection PyTypeChecker
161 entry = AuditEntry(
162 when_access_utc=now,
163 source=source,
164 remote_addr=remote_addr,
165 user_id=user_id,
166 device_id=device_id,
167 table_name=table,
168 server_pk=server_pk,
169 patient_server_pk=patient_server_pk,
170 details=details,
171 )
172 dbsession.add(entry)