PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
sqlite_security_storage.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
17
18#ifdef PACS_WITH_DATABASE_SYSTEM
19
20#include <database/query_builder.h>
21
22#include <variant>
23
24namespace kcenon::pacs::storage {
25
26using namespace security;
27using kcenon::common::make_error;
28using kcenon::common::ok;
29
30namespace {
31constexpr int kSqliteError = 1;
32constexpr int kUserNotFound = 2;
33constexpr int kDatabaseNotConnected = 4;
34} // namespace
35
36sqlite_security_storage::sqlite_security_storage(std::string db_path)
37 : db_path_(std::move(db_path)) {
38 if (auto res = initialize_database(); res.is_err()) {
39 // Log error - initialization failed
40 }
41}
42
43sqlite_security_storage::~sqlite_security_storage() {
44 if (db_manager_) {
45 (void)db_manager_->disconnect_result();
46 }
47}
48
49auto sqlite_security_storage::initialize_database() -> VoidResult {
50 db_context_ = std::make_shared<database::database_context>();
51 db_manager_ = std::make_shared<database::database_manager>(db_context_);
52
53 if (!db_manager_->set_mode(database::database_types::sqlite)) {
54 return make_error<std::monostate>(kSqliteError, "Failed to set SQLite mode",
55 "sqlite_security_storage");
56 }
57
58 auto connect_result = db_manager_->connect_result(db_path_);
59 if (connect_result.is_err()) {
60 return make_error<std::monostate>(
61 kSqliteError, "Failed to connect: " + connect_result.error().message,
62 "sqlite_security_storage");
63 }
64
65 // Create tables using raw SQL (DDL is safe - no user input)
66 const char *create_tables_sql = R"(
67 CREATE TABLE IF NOT EXISTS users (
68 id TEXT PRIMARY KEY,
69 username TEXT UNIQUE NOT NULL,
70 active INTEGER DEFAULT 1
71 );
72 CREATE TABLE IF NOT EXISTS user_roles (
73 user_id TEXT,
74 role TEXT,
75 PRIMARY KEY (user_id, role),
76 FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
77 );
78 )";
79
80 auto exec_result = db_manager_->execute_query_result(create_tables_sql);
81 if (exec_result.is_err()) {
82 return make_error<std::monostate>(
83 kSqliteError, "Failed to create tables: " + exec_result.error().message,
84 "sqlite_security_storage");
85 }
86
87 return ok();
88}
89
90auto sqlite_security_storage::create_user(const User &user) -> VoidResult {
91 if (!db_manager_) {
92 return make_error<std::monostate>(kDatabaseNotConnected,
93 "Database not connected",
94 "sqlite_security_storage");
95 }
96
97 // Use query_builder for SQL injection protection
98 database::query_builder builder(database::database_types::sqlite);
99 auto insert_sql = builder
100 .insert_into("users")
101 .values({{"id", user.id},
102 {"username", user.username},
103 {"active", user.active ? 1 : 0}})
104 .build();
105
106 auto result = db_manager_->execute_query_result(insert_sql);
107 if (result.is_err()) {
108 return make_error<std::monostate>(
109 kSqliteError, "Failed to insert user: " + result.error().message,
110 "sqlite_security_storage");
111 }
112
113 // Insert roles using query_builder
114 for (const auto &role : user.roles) {
115 database::query_builder role_builder(database::database_types::sqlite);
116 auto role_sql = role_builder
117 .insert_into("user_roles")
118 .values({{"user_id", user.id},
119 {"role", std::string(to_string(role))}})
120 .build();
121
122 auto role_result = db_manager_->execute_query_result(role_sql);
123 if (role_result.is_err()) {
124 // Log warning but continue (best effort for roles)
125 }
126 }
127
128 return ok();
129}
130
131auto sqlite_security_storage::get_user(std::string_view id) -> Result<User> {
132 if (!db_manager_) {
133 return make_error<User>(kDatabaseNotConnected, "Database not connected",
134 "sqlite_security_storage");
135 }
136
137 User user;
138 user.id = std::string(id);
139
140 // Use query_builder for SQL injection protection
141 database::query_builder builder(database::database_types::sqlite);
142 auto select_sql =
143 builder.select(std::vector<std::string>{"username", "active"})
144 .from("users")
145 .where("id", "=", std::string(id))
146 .build();
147
148 auto result = db_manager_->select_query_result(select_sql);
149 if (result.is_err()) {
150 return make_error<User>(kSqliteError,
151 "DB Error: " + result.error().message,
152 "sqlite_security_storage");
153 }
154
155 const auto &rows = result.value();
156 if (rows.empty()) {
157 return make_error<User>(kUserNotFound, "User not found",
158 "sqlite_security_storage");
159 }
160
161 // Extract user data from first row
162 const auto &row = rows[0];
163 if (auto it = row.find("username"); it != row.end()) {
164 if (std::holds_alternative<std::string>(it->second)) {
165 user.username = std::get<std::string>(it->second);
166 }
167 }
168 if (auto it = row.find("active"); it != row.end()) {
169 if (std::holds_alternative<int64_t>(it->second)) {
170 user.active = std::get<int64_t>(it->second) != 0;
171 }
172 }
173
174 // Get roles using query_builder
175 database::query_builder role_builder(database::database_types::sqlite);
176 auto role_sql = role_builder.select(std::vector<std::string>{"role"})
177 .from("user_roles")
178 .where("user_id", "=", std::string(id))
179 .build();
180
181 auto role_result = db_manager_->select_query_result(role_sql);
182 if (role_result.is_ok()) {
183 for (const auto &role_row : role_result.value()) {
184 if (auto it = role_row.find("role"); it != role_row.end()) {
185 if (std::holds_alternative<std::string>(it->second)) {
186 if (auto role = parse_role(std::get<std::string>(it->second))) {
187 user.roles.push_back(*role);
188 }
189 }
190 }
191 }
192 }
193
194 return user;
195}
196
197auto sqlite_security_storage::get_user_by_username(std::string_view username)
198 -> Result<User> {
199 if (!db_manager_) {
200 return make_error<User>(kDatabaseNotConnected, "Database not connected",
201 "sqlite_security_storage");
202 }
203
204 // Use query_builder for SQL injection protection
205 database::query_builder builder(database::database_types::sqlite);
206 auto select_sql =
207 builder.select(std::vector<std::string>{"id", "username", "active"})
208 .from("users")
209 .where("username", "=", std::string(username))
210 .build();
211
212 auto result = db_manager_->select_query_result(select_sql);
213 if (result.is_err()) {
214 return make_error<User>(kSqliteError,
215 "DB Error: " + result.error().message,
216 "sqlite_security_storage");
217 }
218
219 const auto &rows = result.value();
220 if (rows.empty()) {
221 return make_error<User>(kUserNotFound, "User not found",
222 "sqlite_security_storage");
223 }
224
225 User user;
226 const auto &row = rows[0];
227
228 if (auto it = row.find("id"); it != row.end()) {
229 if (std::holds_alternative<std::string>(it->second)) {
230 user.id = std::get<std::string>(it->second);
231 }
232 }
233 if (auto it = row.find("username"); it != row.end()) {
234 if (std::holds_alternative<std::string>(it->second)) {
235 user.username = std::get<std::string>(it->second);
236 }
237 }
238 if (auto it = row.find("active"); it != row.end()) {
239 if (std::holds_alternative<int64_t>(it->second)) {
240 user.active = std::get<int64_t>(it->second) != 0;
241 }
242 }
243
244 // Get roles using query_builder
245 database::query_builder role_builder(database::database_types::sqlite);
246 auto role_sql = role_builder.select(std::vector<std::string>{"role"})
247 .from("user_roles")
248 .where("user_id", "=", user.id)
249 .build();
250
251 auto role_result = db_manager_->select_query_result(role_sql);
252 if (role_result.is_ok()) {
253 for (const auto &role_row : role_result.value()) {
254 if (auto it = role_row.find("role"); it != role_row.end()) {
255 if (std::holds_alternative<std::string>(it->second)) {
256 if (auto role = parse_role(std::get<std::string>(it->second))) {
257 user.roles.push_back(*role);
258 }
259 }
260 }
261 }
262 }
263
264 return user;
265}
266
267auto sqlite_security_storage::update_user(const User &user) -> VoidResult {
268 if (!db_manager_) {
269 return make_error<std::monostate>(kDatabaseNotConnected,
270 "Database not connected",
271 "sqlite_security_storage");
272 }
273
274 // Use transaction for atomicity
275 auto tx_result = db_manager_->begin_transaction();
276 if (tx_result.is_err()) {
277 return make_error<std::monostate>(
278 kSqliteError,
279 "Failed to begin transaction: " + tx_result.error().message,
280 "sqlite_security_storage");
281 }
282
283 // Update user using query_builder
284 database::query_builder update_builder(database::database_types::sqlite);
285 auto update_sql = update_builder.update("users")
286 .set({{"active", user.active ? 1 : 0}})
287 .where("id", "=", user.id)
288 .build();
289
290 auto update_result = db_manager_->execute_query_result(update_sql);
291 if (update_result.is_err()) {
292 (void)db_manager_->rollback_transaction();
293 return make_error<std::monostate>(
294 kSqliteError, "Failed to update user: " + update_result.error().message,
295 "sqlite_security_storage");
296 }
297
298 // Delete existing roles using query_builder
299 database::query_builder delete_builder(database::database_types::sqlite);
300 auto delete_sql = delete_builder.delete_from("user_roles")
301 .where("user_id", "=", user.id)
302 .build();
303
304 auto delete_result = db_manager_->execute_query_result(delete_sql);
305 if (delete_result.is_err()) {
306 (void)db_manager_->rollback_transaction();
307 return make_error<std::monostate>(
308 kSqliteError,
309 "Failed to delete roles: " + delete_result.error().message,
310 "sqlite_security_storage");
311 }
312
313 // Insert new roles using query_builder
314 for (const auto &role : user.roles) {
315 database::query_builder role_builder(database::database_types::sqlite);
316 auto role_sql =
317 role_builder.insert_into("user_roles")
318 .values(
319 {{"user_id", user.id}, {"role", std::string(to_string(role))}})
320 .build();
321
322 auto role_result = db_manager_->execute_query_result(role_sql);
323 if (role_result.is_err()) {
324 (void)db_manager_->rollback_transaction();
325 return make_error<std::monostate>(
326 kSqliteError,
327 "Failed to insert role: " + role_result.error().message,
328 "sqlite_security_storage");
329 }
330 }
331
332 auto commit_result = db_manager_->commit_transaction();
333 if (commit_result.is_err()) {
334 (void)db_manager_->rollback_transaction();
335 return make_error<std::monostate>(
336 kSqliteError,
337 "Failed to commit transaction: " + commit_result.error().message,
338 "sqlite_security_storage");
339 }
340
341 return ok();
342}
343
344auto sqlite_security_storage::delete_user(std::string_view id) -> VoidResult {
345 if (!db_manager_) {
346 return make_error<std::monostate>(kDatabaseNotConnected,
347 "Database not connected",
348 "sqlite_security_storage");
349 }
350
351 // Use query_builder for SQL injection protection
352 database::query_builder builder(database::database_types::sqlite);
353 auto delete_sql = builder.delete_from("users")
354 .where("id", "=", std::string(id))
355 .build();
356
357 auto result = db_manager_->execute_query_result(delete_sql);
358 if (result.is_err()) {
359 return make_error<std::monostate>(
360 kSqliteError, "Failed to delete user: " + result.error().message,
361 "sqlite_security_storage");
362 }
363
364 return ok();
365}
366
367auto sqlite_security_storage::get_users_by_role(Role role)
369 if (!db_manager_) {
370 return make_error<std::vector<User>>(kDatabaseNotConnected,
371 "Database not connected",
372 "sqlite_security_storage");
373 }
374
375 // Use query_builder for SQL injection protection
376 database::query_builder builder(database::database_types::sqlite);
377 auto select_sql =
378 builder
379 .select(std::vector<std::string>{"u.id", "u.username", "u.active"})
380 .from("users u")
381 .join("user_roles ur", "u.id = ur.user_id")
382 .where("ur.role", "=", std::string(to_string(role)))
383 .build();
384
385 auto result = db_manager_->select_query_result(select_sql);
386 if (result.is_err()) {
387 return make_error<std::vector<User>>(
388 kSqliteError, "DB Error: " + result.error().message,
389 "sqlite_security_storage");
390 }
391
392 std::vector<User> users;
393 for (const auto &row : result.value()) {
394 User user;
395 if (auto it = row.find("id"); it != row.end()) {
396 if (std::holds_alternative<std::string>(it->second)) {
397 user.id = std::get<std::string>(it->second);
398 }
399 }
400 if (auto it = row.find("username"); it != row.end()) {
401 if (std::holds_alternative<std::string>(it->second)) {
402 user.username = std::get<std::string>(it->second);
403 }
404 }
405 if (auto it = row.find("active"); it != row.end()) {
406 if (std::holds_alternative<int64_t>(it->second)) {
407 user.active = std::get<int64_t>(it->second) != 0;
408 }
409 }
410
411 // Fetch all roles for this user using query_builder
412 database::query_builder role_builder(database::database_types::sqlite);
413 auto role_sql =
414 role_builder.select(std::vector<std::string>{"role"})
415 .from("user_roles")
416 .where("user_id", "=", user.id)
417 .build();
418
419 auto role_result = db_manager_->select_query_result(role_sql);
420 if (role_result.is_ok()) {
421 for (const auto &role_row : role_result.value()) {
422 if (auto it = role_row.find("role"); it != role_row.end()) {
423 if (std::holds_alternative<std::string>(it->second)) {
424 if (auto r = parse_role(std::get<std::string>(it->second))) {
425 user.roles.push_back(*r);
426 }
427 }
428 }
429 }
430 }
431
432 users.push_back(std::move(user));
433 }
434
435 return users;
436}
437
438} // namespace kcenon::pacs::storage
439
440#endif // PACS_WITH_DATABASE_SYSTEM
constexpr dicom_tag rows
Rows.
@ move
C-MOVE move request/response.
std::optional< Role > parse_role(std::string_view str)
Parse Role from string.
Definition role.h:50
kcenon::common::Result< T > Result
Result type alias for operations returning a value.
auto to_string(annotation_type type) -> std::string
Convert annotation_type to string.
SQLite implementation of security storage with SQL injection protection.