Coverage for cc_modules/cc_serversettings.py : 72%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
3"""
4camcops_server/cc_modules/cc_serversettings.py
6===============================================================================
8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com).
10 This file is part of CamCOPS.
12 CamCOPS is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
17 CamCOPS is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
25===============================================================================
27**Represents server-wide configuration settings.**
29Previously, we had a key/value pair system, both for device stored variables
30(table "storedvars") and server ones ("_server_storedvars"). We used a "type"
31column to indicate type, and then columns named "valueInteger", "valueText",
32"valueReal" for the actual values.
34Subsequently
36- There's no need for devices to upload their settings here, so that table
37 goes.
39- The server stored vars stored
41.. code-block:: none
43 idDescription1 - idDescription8 } now have their own table
44 idShortDescription1 - idShortDescription8 }
46 idPolicyUpload } now part of Group definition
47 idPolicyFinalize }
49 lastAnalyticsSentAt now unused
51 serverCamcopsVersion unnecessary (is in code)
53 databaseTitle still needed somehow
55So, two options:
56https://stackoverflow.com/questions/2300356/using-a-single-row-configuration-table-in-sql-server-database-bad-idea
58Let's use a single row, based on a fixed PK (of 1).
60On some databases, you can constrain the PK value to enforce "one row only";
61MySQL isn't one of those.
63- https://docs.sqlalchemy.org/en/latest/core/constraints.html#check-constraint
65- https://stackoverflow.com/questions/3967372/sql-server-how-to-constrain-a-table-to-contain-a-single-row
67""" # noqa
69import logging
70from typing import Optional, TYPE_CHECKING
72from cardinal_pythonlib.logs import BraceStyleAdapter
73import pendulum
74from pendulum import DateTime as Pendulum
75from sqlalchemy.sql.schema import Column, MetaData, Table
76from sqlalchemy.sql.sqltypes import (
77 DateTime, Float, Integer, String, UnicodeText,
78)
80from camcops_server.cc_modules.cc_sqla_coltypes import DatabaseTitleColType
81from camcops_server.cc_modules.cc_sqlalchemy import Base
83if TYPE_CHECKING:
84 from datetime import datetime
85 from camcops_server.cc_modules.cc_request import CamcopsRequest
87log = BraceStyleAdapter(logging.getLogger(__name__))
90# =============================================================================
91# ServerStoredVars - defunct, but maintained for database imports
92# =============================================================================
94class StoredVarTypesDefunct(object):
95 """
96 Variable types for the ServerStoredVars system.
98 Defunct, but maintained for database imports.
99 """
100 # values for the "type" column
101 TYPE_INTEGER = "integer"
102 TYPE_TEXT = "text"
103 TYPE_REAL = "real"
106class ServerStoredVarNamesDefunct(object):
107 """
108 Variable names for the ServerStoredVars system.
110 Defunct, but maintained for database imports.
111 """
112 # values for the "name" column
113 ID_POLICY_UPLOAD = "idPolicyUpload" # text
114 ID_POLICY_FINALIZE = "idPolicyFinalize" # text
115 SERVER_CAMCOPS_VERSION = "serverCamcopsVersion" # text
116 DATABASE_TITLE = "databaseTitle" # text
117 LAST_ANALYTICS_SENT_AT = "lastAnalyticsSentAt" # text
118 ID_DESCRIPTION_PREFIX = "idDescription" # text; apply suffixes 1-8
119 ID_SHORT_DESCRIPTION_PREFIX = "idShortDescription" # text; apply suffixes 1-8 # noqa
122StoredVarNameColTypeDefunct = String(length=255)
123StoredVarTypeColTypeDefunct = String(length=255)
124_ssv_metadata = MetaData()
127server_stored_var_table_defunct = Table(
128 "_server_storedvars", # table name
129 _ssv_metadata, # metadata separate from everything else
130 Column(
131 "name", StoredVarNameColTypeDefunct,
132 primary_key=True, index=True,
133 comment="Variable name"
134 ),
135 Column(
136 "type", StoredVarTypeColTypeDefunct,
137 nullable=False,
138 comment="Variable type ('integer', 'real', 'text')"
139 ),
140 Column(
141 "valueInteger", Integer,
142 comment="Value of an integer variable"
143 ),
144 Column(
145 "valueText", UnicodeText,
146 comment="Value of a text variable"
147 ),
148 Column(
149 "valueReal", Float,
150 comment="Value of a real (floating-point) variable"
151 )
152)
155# =============================================================================
156# ServerSettings
157# =============================================================================
159SERVER_SETTINGS_SINGLETON_PK = 1
160# CACHE_KEY_DATABASE_TITLE = "database_title"
163class ServerSettings(Base):
164 """
165 Singleton SQLAlchemy object (i.e. there is just one row in the database
166 table) representing server settings.
167 """
168 __tablename__ = "_server_settings"
170 id = Column(
171 "id", Integer,
172 primary_key=True, autoincrement=True, index=True,
173 comment=(
174 f"PK (arbitrary integer but only a value of "
175 f"{SERVER_SETTINGS_SINGLETON_PK} is ever used)"
176 )
177 )
178 database_title = Column(
179 "database_title", DatabaseTitleColType,
180 comment="Database title"
181 )
182 last_dummy_login_failure_clearance_at_utc = Column(
183 "last_dummy_login_failure_clearance_at_utc", DateTime,
184 comment="Date/time (in UTC) when login failure records were cleared "
185 "for nonexistent users (security feature)"
186 )
188 def get_last_dummy_login_failure_clearance_pendulum(self) \
189 -> Optional[Pendulum]:
190 """
191 Returns the time at which login failure records were cleared for
192 nonexistent users.
194 This is part of a security failure to prevent attackers discovering
195 usernames: since repeated attempts to hack a real account leads to an
196 account lockout, we arrange things so that attempts to hack nonexistent
197 accounts do likewise.
199 Specifically, this function returns an offset-aware (timezone-aware)
200 version of the raw UTC DATETIME from the database.
201 """
202 dt = self.last_dummy_login_failure_clearance_at_utc # type: Optional[datetime] # noqa
203 if dt is None:
204 return None
205 return pendulum.instance(dt, tz=pendulum.UTC)
208def get_server_settings(req: "CamcopsRequest") -> ServerSettings:
209 """
210 Gets the
211 :class:`camcops_server.cc_modules.cc_serversettings.ServerSettings` object
212 for the request.
213 """
214 dbsession = req.dbsession
215 server_settings = dbsession.query(ServerSettings)\
216 .filter(ServerSettings.id == SERVER_SETTINGS_SINGLETON_PK)\
217 .first()
218 if server_settings is None:
219 server_settings = ServerSettings()
220 server_settings.id = SERVER_SETTINGS_SINGLETON_PK
221 server_settings.database_title = "DATABASE_TITLE_UNSET"
222 dbsession.add(server_settings)
223 return server_settings
226# def get_database_title(req: "CamcopsRequest") -> str:
227# def creator() -> str:
228# server_settings = get_server_settings(req)
229# return server_settings.database_title or ""
230#
231# return cache_region_static.get_or_create(CACHE_KEY_DATABASE_TITLE, creator) # noqa
234# def clear_database_title_cache() -> None:
235# cache_region_static.delete(CACHE_KEY_DATABASE_TITLE)
238# def set_database_title(req: "CamcopsRequest", title: str) -> None:
239# server_settings = get_server_settings(req)
240# server_settings.database_title = title
241# clear_database_title_cache()