20#ifndef ORES_IAM_MESSAGING_ROLE_HANDLER_HPP
21#define ORES_IAM_MESSAGING_ROLE_HANDLER_HPP
23#include <boost/uuid/string_generator.hpp>
24#include <boost/uuid/uuid_io.hpp>
25#include "ores.logging/make_logger.hpp"
26#include "ores.nats/domain/message.hpp"
27#include "ores.nats/service/client.hpp"
28#include "ores.database/domain/context.hpp"
29#include "ores.database/service/tenant_context.hpp"
30#include "ores.database/repository/bitemporal_operations.hpp"
31#include "ores.security/jwt/jwt_authenticator.hpp"
32#include "ores.service/messaging/handler_helpers.hpp"
33#include "ores.service/service/request_context.hpp"
34#include "ores.iam.api/messaging/authorization_protocol.hpp"
35#include "ores.iam.api/domain/permission.hpp"
36#include "ores.iam.core/repository/account_repository.hpp"
37#include "ores.iam.core/repository/tenant_repository.hpp"
38#include "ores.iam.core/service/authorization_service.hpp"
40namespace ores::iam::messaging {
44inline auto& role_handler_lg() {
45 static auto instance = ores::logging::make_logger(
46 "ores.iam.messaging.role_handler");
52using ores::service::messaging::reply;
53using ores::service::messaging::decode;
54using ores::service::messaging::log_handler_entry;
56using ores::service::messaging::error_reply;
63 : nats_(nats), ctx_(
std::move(ctx)), signer_(
std::move(signer)) {}
66 [[maybe_unused]]
const auto correlation_id =
67 log_handler_entry(role_handler_lg(), msg);
69 auto ctx_expected = ores::service::service::make_request_context(
70 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
72 error_reply(nats_, msg, ctx_expected.error());
75 const auto& ctx = *ctx_expected;
76 service::authorization_service svc(ctx);
77 auto roles = svc.list_roles();
78 BOOST_LOG_SEV(role_handler_lg(), debug)
81 list_roles_response{.roles = std::move(roles)});
82 }
catch (
const std::exception& e) {
83 BOOST_LOG_SEV(role_handler_lg(), error)
84 << msg.
subject <<
" failed: " << e.what();
85 reply(nats_, msg, list_roles_response{});
90 [[maybe_unused]]
const auto correlation_id =
91 log_handler_entry(role_handler_lg(), msg);
92 auto req = decode<assign_role_request>(msg);
94 BOOST_LOG_SEV(role_handler_lg(), warn)
95 <<
"Failed to decode: " << msg.
subject;
99 auto ctx_expected = ores::service::service::make_request_context(
100 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
102 error_reply(nats_, msg, ctx_expected.error());
105 const auto& ctx = *ctx_expected;
106 service::authorization_service svc(ctx);
107 boost::uuids::string_generator sg;
108 svc.assign_role(sg(req->account_id),
109 sg(req->role_id), ctx.actor());
110 BOOST_LOG_SEV(role_handler_lg(), debug)
111 <<
"Completed " << msg.
subject;
113 assign_role_response{.success =
true});
114 }
catch (
const std::exception& e) {
115 BOOST_LOG_SEV(role_handler_lg(), error)
116 << msg.
subject <<
" failed: " << e.what();
117 reply(nats_, msg, assign_role_response{
119 .error_message = e.what()});
124 [[maybe_unused]]
const auto correlation_id =
125 log_handler_entry(role_handler_lg(), msg);
126 auto req = decode<revoke_role_request>(msg);
128 BOOST_LOG_SEV(role_handler_lg(), warn)
129 <<
"Failed to decode: " << msg.
subject;
133 auto ctx_expected = ores::service::service::make_request_context(
134 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
136 error_reply(nats_, msg, ctx_expected.error());
139 const auto& ctx = *ctx_expected;
140 boost::uuids::string_generator sg;
141 const auto caller_id = sg(ctx.actor());
142 service::authorization_service svc(ctx);
143 if (!svc.has_permission(caller_id,
144 domain::permissions::roles_revoke)) {
145 BOOST_LOG_SEV(role_handler_lg(), warn)
147 <<
" denied: caller lacks iam::roles:revoke permission";
148 reply(nats_, msg, revoke_role_response{
151 "Permission denied: iam::roles:revoke required"});
154 svc.revoke_role(sg(req->account_id), sg(req->role_id));
155 BOOST_LOG_SEV(role_handler_lg(), debug)
156 <<
"Completed " << msg.
subject;
158 revoke_role_response{.success =
true});
159 }
catch (
const std::exception& e) {
160 BOOST_LOG_SEV(role_handler_lg(), error)
161 << msg.
subject <<
" failed: " << e.what();
162 reply(nats_, msg, revoke_role_response{
164 .error_message = e.what()});
169 [[maybe_unused]]
const auto correlation_id =
170 log_handler_entry(role_handler_lg(), msg);
171 auto req = decode<get_account_roles_request>(msg);
173 BOOST_LOG_SEV(role_handler_lg(), warn)
174 <<
"Failed to decode: " << msg.
subject;
178 auto ctx_expected = ores::service::service::make_request_context(
179 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
181 error_reply(nats_, msg, ctx_expected.error());
184 const auto& ctx = *ctx_expected;
185 service::authorization_service svc(ctx);
186 boost::uuids::string_generator sg;
187 auto roles = svc.get_account_roles(
188 sg(req->account_id));
189 BOOST_LOG_SEV(role_handler_lg(), debug)
190 <<
"Completed " << msg.
subject;
192 get_account_roles_response{
193 .roles = std::move(roles)});
194 }
catch (
const std::exception& e) {
195 BOOST_LOG_SEV(role_handler_lg(), error)
196 << msg.
subject <<
" failed: " << e.what();
197 reply(nats_, msg, get_account_roles_response{});
202 [[maybe_unused]]
const auto correlation_id =
203 log_handler_entry(role_handler_lg(), msg);
204 auto req = decode<assign_role_by_name_request>(msg);
206 BOOST_LOG_SEV(role_handler_lg(), warn)
207 <<
"Failed to decode: " << msg.
subject;
211 auto ctx_expected = ores::service::service::make_request_context(
212 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
214 error_reply(nats_, msg, ctx_expected.error());
217 const auto& ctx = *ctx_expected;
220 const auto at_pos = req->principal.rfind(
'@');
221 if (at_pos == std::string::npos) {
222 reply(nats_, msg, assign_role_by_name_response{
225 "Principal must be in username@hostname format"});
228 const auto username = req->principal.substr(0, at_pos);
229 const auto hostname = req->principal.substr(at_pos + 1);
232 repository::tenant_repository tenant_repo(ctx_);
233 auto tenants = tenant_repo.read_latest_by_hostname(hostname);
234 if (tenants.empty()) {
235 reply(nats_, msg, assign_role_by_name_response{
238 "Tenant not found for hostname: " + hostname});
243 auto tenant_ctx = tenant_context::with_tenant(
244 ctx_, boost::uuids::to_string(tenants.front().id));
247 repository::account_repository acct_repo(tenant_ctx);
248 auto accounts = acct_repo.read_latest_by_username(username);
249 if (accounts.empty()) {
250 reply(nats_, msg, assign_role_by_name_response{
252 .error_message =
"Account not found: " + username});
257 service::authorization_service auth_svc(tenant_ctx);
258 auto role = auth_svc.find_role_by_name(req->role_name);
260 reply(nats_, msg, assign_role_by_name_response{
262 .error_message =
"Role not found: " + req->role_name});
266 auth_svc.assign_role(accounts.front().id, role->id, ctx.actor());
267 BOOST_LOG_SEV(role_handler_lg(), debug)
268 <<
"Completed " << msg.
subject;
269 reply(nats_, msg, assign_role_by_name_response{.success =
true});
270 }
catch (
const std::exception& e) {
271 BOOST_LOG_SEV(role_handler_lg(), error)
272 << msg.
subject <<
" failed: " << e.what();
273 reply(nats_, msg, assign_role_by_name_response{
275 .error_message = e.what()});
280 [[maybe_unused]]
const auto correlation_id =
281 log_handler_entry(role_handler_lg(), msg);
282 auto req = decode<revoke_role_by_name_request>(msg);
284 BOOST_LOG_SEV(role_handler_lg(), warn)
285 <<
"Failed to decode: " << msg.
subject;
290 auto ctx_expected = ores::service::service::make_request_context(
291 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
293 error_reply(nats_, msg, ctx_expected.error());
296 const auto& ctx = *ctx_expected;
297 boost::uuids::string_generator sg;
298 const auto caller_id = sg(ctx.actor());
299 service::authorization_service caller_svc(ctx);
300 if (!caller_svc.has_permission(caller_id,
301 domain::permissions::roles_revoke)) {
302 BOOST_LOG_SEV(role_handler_lg(), warn)
304 <<
" denied: caller lacks iam::roles:revoke permission";
305 reply(nats_, msg, revoke_role_by_name_response{
308 "Permission denied: iam::roles:revoke required"});
313 const auto at_pos = req->principal.rfind(
'@');
314 if (at_pos == std::string::npos) {
315 reply(nats_, msg, revoke_role_by_name_response{
318 "Principal must be in username@hostname format"});
321 const auto username = req->principal.substr(0, at_pos);
322 const auto hostname = req->principal.substr(at_pos + 1);
325 repository::tenant_repository tenant_repo(ctx_);
326 auto tenants = tenant_repo.read_latest_by_hostname(hostname);
327 if (tenants.empty()) {
328 reply(nats_, msg, revoke_role_by_name_response{
331 "Tenant not found for hostname: " + hostname});
336 auto tenant_ctx = tenant_context::with_tenant(
337 ctx_, boost::uuids::to_string(tenants.front().id));
340 repository::account_repository acct_repo(tenant_ctx);
341 auto accounts = acct_repo.read_latest_by_username(username);
342 if (accounts.empty()) {
343 reply(nats_, msg, revoke_role_by_name_response{
345 .error_message =
"Account not found: " + username});
350 service::authorization_service auth_svc(tenant_ctx);
351 auto role = auth_svc.find_role_by_name(req->role_name);
353 reply(nats_, msg, revoke_role_by_name_response{
355 .error_message =
"Role not found: " + req->role_name});
359 auth_svc.revoke_role(accounts.front().id, role->id);
360 BOOST_LOG_SEV(role_handler_lg(), debug)
361 <<
"Completed " << msg.
subject;
362 reply(nats_, msg, revoke_role_by_name_response{.success =
true});
363 }
catch (
const std::exception& e) {
364 BOOST_LOG_SEV(role_handler_lg(), error)
365 << msg.
subject <<
" failed: " << e.what();
366 reply(nats_, msg, revoke_role_by_name_response{
368 .error_message = e.what()});
373 [[maybe_unused]]
const auto correlation_id =
374 log_handler_entry(role_handler_lg(), msg);
375 auto req = decode<suggest_role_commands_request>(msg);
377 BOOST_LOG_SEV(role_handler_lg(), warn)
378 <<
"Failed to decode: " << msg.
subject;
382 using ores::database::repository::execute_parameterized_string_query;
383 std::vector<std::string> results;
384 if (!req->tenant_id.empty()) {
385 results = execute_parameterized_string_query(ctx_,
386 "SELECT command FROM "
387 "ores_iam_generate_role_commands_fn($1, NULL, $2::uuid)",
388 {req->username, req->tenant_id},
389 role_handler_lg(),
"Suggest role commands by tenant_id");
390 }
else if (!req->hostname.empty()) {
391 results = execute_parameterized_string_query(ctx_,
392 "SELECT command FROM "
393 "ores_iam_generate_role_commands_fn($1, $2)",
394 {req->username, req->hostname},
395 role_handler_lg(),
"Suggest role commands by hostname");
397 reply(nats_, msg, suggest_role_commands_response{});
400 BOOST_LOG_SEV(role_handler_lg(), debug)
401 <<
"Completed " << msg.
subject;
402 reply(nats_, msg, suggest_role_commands_response{
403 .commands = std::move(results)});
404 }
catch (
const std::exception& e) {
405 BOOST_LOG_SEV(role_handler_lg(), error)
406 << msg.
subject <<
" failed: " << e.what();
407 reply(nats_, msg, suggest_role_commands_response{});
Implements logging infrastructure for ORE Studio.
Definition boost_severity.hpp:28
Context for the operations on a postgres database.
Definition context.hpp:47
Manages tenant context for multi-tenant database operations.
Definition tenant_context.hpp:37
A received NATS message.
Definition message.hpp:40
std::string subject
The subject the message was published to.
Definition message.hpp:44
NATS client: connection, pub/sub, request/reply, and JetStream.
Definition client.hpp:73
JWT authentication primitive.
Definition jwt_authenticator.hpp:45