PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
access_control_manager.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
13
14namespace kcenon::pacs::security {
15
19
20// =============================================================================
21// DICOM Operation Mapping
22// =============================================================================
23
24std::pair<ResourceType, std::uint32_t>
52
54 // Viewer: Read-only access to Studies
57
58 // Technologist: Read access, Create/Update studies (but not delete)
62
63 // Radiologist: Same as Technologist + Verification (represented as Write in
64 // this simple model or could be separate) Giving Full study access for now
65 // (Delete/Export)
69
70 // Administrator: User management, System config
78
79 // System: Internal superuser
85}
86
88 std::shared_ptr<security_storage_interface> storage) {
89 storage_ = std::move(storage);
90}
91
93 -> kcenon::common::VoidResult {
94 if (!storage_)
95 return kcenon::common::make_error<std::monostate>(
96 1, "Storage not configured", "access_control");
97 return storage_->create_user(user);
98}
99
100auto access_control_manager::get_user(std::string_view id)
102 if (!storage_)
103 return kcenon::common::make_error<User>(1, "Storage not configured",
104 "access_control");
105 return storage_->get_user(id);
106}
107
108auto access_control_manager::assign_role(std::string_view user_id, Role role)
109 -> kcenon::common::VoidResult {
110 if (!storage_)
111 return kcenon::common::make_error<std::monostate>(
112 1, "Storage not configured", "access_control");
113
114 // Get user, add role, update
115 auto user_res = storage_->get_user(user_id);
116 if (user_res.is_err())
117 return kcenon::common::make_error<std::monostate>(2, "User not found",
118 "access_control");
119
120 auto user = user_res.unwrap();
121 if (!user.has_role(role)) {
122 user.roles.push_back(role);
123 return storage_->update_user(user);
124 }
125 return kcenon::common::ok();
126}
127
130 std::uint32_t action_mask) const {
131 if (!user.active)
132 return false;
133
134 for (const auto &role : user.roles) {
135 auto it = role_permissions_.find(role);
136 if (it != role_permissions_.end()) {
137 for (const auto &perm : it->second) {
138 if (perm.resource == resource && perm.has_action(action_mask)) {
139 return true;
140 }
141 }
142 }
143 }
144 return false;
145}
146
147bool access_control_manager::has_role(const User &user, Role role) const {
148 return user.has_role(role);
149}
150
152 Role role, std::vector<Permission> permissions) {
153 role_permissions_[role] = std::move(permissions);
154}
155
156const std::vector<Permission> &
158 static const std::vector<Permission> empty;
159 auto it = role_permissions_.find(role);
160 if (it != role_permissions_.end()) {
161 return it->second;
162 }
163 return empty;
164}
165
166// =============================================================================
167// DICOM Access Control
168// =============================================================================
169
172 std::uint32_t action_mask)
173 -> kcenon::common::VoidResult {
174 if (!ctx.is_valid()) {
175 return kcenon::common::make_error<std::monostate>(
176 1, "User context is not valid (inactive user)", "access_control");
177 }
178
179 if (!check_permission(ctx.user(), resource, action_mask)) {
180 return kcenon::common::make_error<std::monostate>(
181 2, "Access denied: insufficient permissions", "access_control");
182 }
183
184 return kcenon::common::ok();
185}
186
189 DicomOperation op) const {
190 if (!ctx.is_valid()) {
191 auto result = AccessCheckResult::deny("User context is not valid");
192 if (audit_callback_) {
193 audit_callback_(ctx, op, result);
194 }
195 return result;
196 }
197
198 auto [resource, action] = map_dicom_operation(op);
199 bool allowed = check_permission(ctx.user(), resource, action);
200
201 AccessCheckResult result =
202 allowed ? AccessCheckResult::allow()
203 : AccessCheckResult::deny("Insufficient permissions for operation");
204
205 if (audit_callback_) {
206 audit_callback_(ctx, op, result);
207 }
208
209 return result;
210}
211
213 std::string_view ae_title, const std::string &session_id) const {
214 std::lock_guard<std::mutex> lock(mutex_);
215
216 // Look up AE Title to user mapping
217 auto it = ae_to_user_id_.find(std::string(ae_title));
218 if (it == ae_to_user_id_.end()) {
219 // Reject unregistered AE titles
220 return std::nullopt;
221 }
222
223 // Get user from storage
224 if (!storage_) {
225 return std::nullopt;
226 }
227
228 auto user_result = storage_->get_user(it->second);
229 if (user_result.is_err()) {
230 return std::nullopt;
231 }
232
233 auto ctx = user_context(user_result.unwrap(), session_id);
234 ctx.set_source_ae_title(std::string(ae_title));
235 return ctx;
236}
237
238// =============================================================================
239// AE Title Mapping
240// =============================================================================
241
242void access_control_manager::register_ae_title(std::string_view ae_title,
243 std::string_view user_id) {
244 std::lock_guard<std::mutex> lock(mutex_);
245 ae_to_user_id_[std::string(ae_title)] = std::string(user_id);
246}
247
248void access_control_manager::unregister_ae_title(std::string_view ae_title) {
249 std::lock_guard<std::mutex> lock(mutex_);
250 ae_to_user_id_.erase(std::string(ae_title));
251}
252
253auto access_control_manager::get_user_by_ae_title(std::string_view ae_title)
254 -> std::optional<User> {
255 std::lock_guard<std::mutex> lock(mutex_);
256
257 auto it = ae_to_user_id_.find(std::string(ae_title));
258 if (it == ae_to_user_id_.end()) {
259 return std::nullopt;
260 }
261
262 if (!storage_) {
263 return std::nullopt;
264 }
265
266 auto user_result = storage_->get_user(it->second);
267 if (user_result.is_err()) {
268 return std::nullopt;
269 }
270
271 return user_result.unwrap();
272}
273
274// =============================================================================
275// Audit Callback
276// =============================================================================
277
279 std::lock_guard<std::mutex> lock(mutex_);
280 audit_callback_ = std::move(callback);
281}
282
283} // namespace kcenon::pacs::security
Core RBAC logic.
auto get_user_by_ae_title(std::string_view ae_title) -> std::optional< User >
Get user by AE Title.
auto create_user(const User &user) -> kcenon::common::VoidResult
bool has_role(const User &user, Role role) const
bool check_permission(const User &user, ResourceType resource, std::uint32_t action_mask) const
static std::pair< ResourceType, std::uint32_t > map_dicom_operation(DicomOperation op)
Map DICOM operation to resource type and action.
AccessCheckResult check_dicom_operation(const user_context &ctx, DicomOperation op) const
Check if a DICOM operation is allowed.
void set_role_permissions(Role role, std::vector< Permission > permissions)
std::map< Role, std::vector< Permission > > role_permissions_
auto get_user(std::string_view id) -> kcenon::common::Result< User >
void register_ae_title(std::string_view ae_title, std::string_view user_id)
void set_storage(std::shared_ptr< security_storage_interface > storage)
auto validate_access(const user_context &ctx, ResourceType resource, std::uint32_t action_mask) -> kcenon::common::VoidResult
Validate access for a user context.
const std::vector< Permission > & get_role_permissions(Role role) const
std::shared_ptr< security_storage_interface > storage_
void set_audit_callback(AccessAuditCallback callback)
std::map< std::string, std::string > ae_to_user_id_
auto assign_role(std::string_view user_id, Role role) -> kcenon::common::VoidResult
std::optional< user_context > get_context_for_ae(std::string_view ae_title, const std::string &session_id) const
Get user context for an AE Title.
Represents the security context for a user session.
bool is_valid() const noexcept
Check if the context is valid (user is active)
const User & user() const noexcept
constexpr std::uint32_t None
Definition permission.h:40
constexpr std::uint32_t Delete
Definition permission.h:43
constexpr std::uint32_t Export
Definition permission.h:44
constexpr std::uint32_t Full
Definition permission.h:49
constexpr std::uint32_t Write
Definition permission.h:42
constexpr std::uint32_t Read
Definition permission.h:41
constexpr std::uint32_t Execute
Definition permission.h:45
Role
User roles in the PACS system.
Definition role.h:25
@ Technologist
Can upload/modify studies, but not delete.
@ Administrator
User management, system config.
@ System
Internal system operations.
@ Radiologist
Full clinical access (includes verification)
@ Viewer
Read-only access to studies.
std::function< void(const user_context &ctx, DicomOperation op, const AccessCheckResult &result)> AccessAuditCallback
Callback for audit logging of access attempts.
@ empty
Z - Replace with zero-length value.
ResourceType
Categories of resources requiring protection.
Definition permission.h:25
@ Study
DICOM studies/series/instances.
@ System
System configuration and services.
@ Metadata
Patient/Study metadata.
DicomOperation
DICOM operation types for permission checking.
static AccessCheckResult deny(std::string reason)
Represents a user in the system.
Definition user.h:26