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

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/cc_audit.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**Auditing.** 

29 

30The Big Brother part. 

31 

32""" 

33 

34from typing import TYPE_CHECKING 

35 

36from sqlalchemy.orm import relationship 

37from sqlalchemy.sql.schema import Column, ForeignKey 

38from sqlalchemy.sql.sqltypes import DateTime, Integer, UnicodeText 

39 

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 

46 

47if TYPE_CHECKING: 

48 from camcops_server.cc_modules.cc_request import CamcopsRequest 

49 

50 

51MAX_AUDIT_STRING_LENGTH = 65000 

52 

53 

54# ============================================================================= 

55# AuditEntry 

56# ============================================================================= 

57 

58 

59class AuditEntry(Base): 

60 """ 

61 An entry in the audit table. 

62 """ 

63 

64 __tablename__ = "_security_audit" 

65 

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. 

124 

125 

126# ============================================================================= 

127# Audit function 

128# ============================================================================= 

129 

130 

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)