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::error_reply;
61 : nats_(nats), ctx_(
std::move(ctx)), signer_(
std::move(signer)) {}
65 BOOST_LOG_SEV(role_handler_lg(), debug)
68 auto ctx_expected = ores::service::service::make_request_context(
69 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
71 error_reply(nats_, msg, ctx_expected.error());
74 const auto& ctx = *ctx_expected;
75 service::authorization_service svc(ctx);
76 auto roles = svc.list_roles();
77 BOOST_LOG_SEV(role_handler_lg(), debug)
80 list_roles_response{.roles = std::move(roles)});
81 }
catch (
const std::exception& e) {
82 BOOST_LOG_SEV(role_handler_lg(), error)
83 << msg.
subject <<
" failed: " << e.what();
84 reply(nats_, msg, list_roles_response{});
90 BOOST_LOG_SEV(role_handler_lg(), debug)
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()});
125 BOOST_LOG_SEV(role_handler_lg(), debug)
127 auto req = decode<revoke_role_request>(msg);
129 BOOST_LOG_SEV(role_handler_lg(), warn)
130 <<
"Failed to decode: " << msg.
subject;
134 auto ctx_expected = ores::service::service::make_request_context(
135 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
137 error_reply(nats_, msg, ctx_expected.error());
140 const auto& ctx = *ctx_expected;
141 boost::uuids::string_generator sg;
142 const auto caller_id = sg(ctx.actor());
143 service::authorization_service svc(ctx);
144 if (!svc.has_permission(caller_id,
145 domain::permissions::roles_revoke)) {
146 BOOST_LOG_SEV(role_handler_lg(), warn)
148 <<
" denied: caller lacks iam::roles:revoke permission";
149 reply(nats_, msg, revoke_role_response{
152 "Permission denied: iam::roles:revoke required"});
155 svc.revoke_role(sg(req->account_id), sg(req->role_id));
156 BOOST_LOG_SEV(role_handler_lg(), debug)
157 <<
"Completed " << msg.
subject;
159 revoke_role_response{.success =
true});
160 }
catch (
const std::exception& e) {
161 BOOST_LOG_SEV(role_handler_lg(), error)
162 << msg.
subject <<
" failed: " << e.what();
163 reply(nats_, msg, revoke_role_response{
165 .error_message = e.what()});
171 BOOST_LOG_SEV(role_handler_lg(), debug)
173 auto req = decode<get_account_roles_request>(msg);
175 BOOST_LOG_SEV(role_handler_lg(), warn)
176 <<
"Failed to decode: " << msg.
subject;
180 auto ctx_expected = ores::service::service::make_request_context(
181 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
183 error_reply(nats_, msg, ctx_expected.error());
186 const auto& ctx = *ctx_expected;
187 service::authorization_service svc(ctx);
188 boost::uuids::string_generator sg;
189 auto roles = svc.get_account_roles(
190 sg(req->account_id));
191 BOOST_LOG_SEV(role_handler_lg(), debug)
192 <<
"Completed " << msg.
subject;
194 get_account_roles_response{
195 .roles = std::move(roles)});
196 }
catch (
const std::exception& e) {
197 BOOST_LOG_SEV(role_handler_lg(), error)
198 << msg.
subject <<
" failed: " << e.what();
199 reply(nats_, msg, get_account_roles_response{});
205 BOOST_LOG_SEV(role_handler_lg(), debug)
207 auto req = decode<assign_role_by_name_request>(msg);
209 BOOST_LOG_SEV(role_handler_lg(), warn)
210 <<
"Failed to decode: " << msg.
subject;
214 auto ctx_expected = ores::service::service::make_request_context(
215 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
217 error_reply(nats_, msg, ctx_expected.error());
220 const auto& ctx = *ctx_expected;
223 const auto at_pos = req->principal.rfind(
'@');
224 if (at_pos == std::string::npos) {
225 reply(nats_, msg, assign_role_by_name_response{
228 "Principal must be in username@hostname format"});
231 const auto username = req->principal.substr(0, at_pos);
232 const auto hostname = req->principal.substr(at_pos + 1);
235 repository::tenant_repository tenant_repo(ctx_);
236 auto tenants = tenant_repo.read_latest_by_hostname(hostname);
237 if (tenants.empty()) {
238 reply(nats_, msg, assign_role_by_name_response{
241 "Tenant not found for hostname: " + hostname});
246 auto tenant_ctx = tenant_context::with_tenant(
247 ctx_, boost::uuids::to_string(tenants.front().id));
250 repository::account_repository acct_repo(tenant_ctx);
251 auto accounts = acct_repo.read_latest_by_username(username);
252 if (accounts.empty()) {
253 reply(nats_, msg, assign_role_by_name_response{
255 .error_message =
"Account not found: " + username});
260 service::authorization_service auth_svc(tenant_ctx);
261 auto role = auth_svc.find_role_by_name(req->role_name);
263 reply(nats_, msg, assign_role_by_name_response{
265 .error_message =
"Role not found: " + req->role_name});
269 auth_svc.assign_role(accounts.front().id, role->id, ctx.actor());
270 BOOST_LOG_SEV(role_handler_lg(), debug)
271 <<
"Completed " << msg.
subject;
272 reply(nats_, msg, assign_role_by_name_response{.success =
true});
273 }
catch (
const std::exception& e) {
274 BOOST_LOG_SEV(role_handler_lg(), error)
275 << msg.
subject <<
" failed: " << e.what();
276 reply(nats_, msg, assign_role_by_name_response{
278 .error_message = e.what()});
284 BOOST_LOG_SEV(role_handler_lg(), debug)
286 auto req = decode<revoke_role_by_name_request>(msg);
288 BOOST_LOG_SEV(role_handler_lg(), warn)
289 <<
"Failed to decode: " << msg.
subject;
294 auto ctx_expected = ores::service::service::make_request_context(
295 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
297 error_reply(nats_, msg, ctx_expected.error());
300 const auto& ctx = *ctx_expected;
301 boost::uuids::string_generator sg;
302 const auto caller_id = sg(ctx.actor());
303 service::authorization_service caller_svc(ctx);
304 if (!caller_svc.has_permission(caller_id,
305 domain::permissions::roles_revoke)) {
306 BOOST_LOG_SEV(role_handler_lg(), warn)
308 <<
" denied: caller lacks iam::roles:revoke permission";
309 reply(nats_, msg, revoke_role_by_name_response{
312 "Permission denied: iam::roles:revoke required"});
317 const auto at_pos = req->principal.rfind(
'@');
318 if (at_pos == std::string::npos) {
319 reply(nats_, msg, revoke_role_by_name_response{
322 "Principal must be in username@hostname format"});
325 const auto username = req->principal.substr(0, at_pos);
326 const auto hostname = req->principal.substr(at_pos + 1);
329 repository::tenant_repository tenant_repo(ctx_);
330 auto tenants = tenant_repo.read_latest_by_hostname(hostname);
331 if (tenants.empty()) {
332 reply(nats_, msg, revoke_role_by_name_response{
335 "Tenant not found for hostname: " + hostname});
340 auto tenant_ctx = tenant_context::with_tenant(
341 ctx_, boost::uuids::to_string(tenants.front().id));
344 repository::account_repository acct_repo(tenant_ctx);
345 auto accounts = acct_repo.read_latest_by_username(username);
346 if (accounts.empty()) {
347 reply(nats_, msg, revoke_role_by_name_response{
349 .error_message =
"Account not found: " + username});
354 service::authorization_service auth_svc(tenant_ctx);
355 auto role = auth_svc.find_role_by_name(req->role_name);
357 reply(nats_, msg, revoke_role_by_name_response{
359 .error_message =
"Role not found: " + req->role_name});
363 auth_svc.revoke_role(accounts.front().id, role->id);
364 BOOST_LOG_SEV(role_handler_lg(), debug)
365 <<
"Completed " << msg.
subject;
366 reply(nats_, msg, revoke_role_by_name_response{.success =
true});
367 }
catch (
const std::exception& e) {
368 BOOST_LOG_SEV(role_handler_lg(), error)
369 << msg.
subject <<
" failed: " << e.what();
370 reply(nats_, msg, revoke_role_by_name_response{
372 .error_message = e.what()});
378 BOOST_LOG_SEV(role_handler_lg(), debug)
380 auto req = decode<suggest_role_commands_request>(msg);
382 BOOST_LOG_SEV(role_handler_lg(), warn)
383 <<
"Failed to decode: " << msg.
subject;
387 using ores::database::repository::execute_parameterized_string_query;
388 std::vector<std::string> results;
389 if (!req->tenant_id.empty()) {
390 results = execute_parameterized_string_query(ctx_,
391 "SELECT command FROM "
392 "ores_iam_generate_role_commands_fn($1, NULL, $2::uuid)",
393 {req->username, req->tenant_id},
394 role_handler_lg(),
"Suggest role commands by tenant_id");
395 }
else if (!req->hostname.empty()) {
396 results = execute_parameterized_string_query(ctx_,
397 "SELECT command FROM "
398 "ores_iam_generate_role_commands_fn($1, $2)",
399 {req->username, req->hostname},
400 role_handler_lg(),
"Suggest role commands by hostname");
402 reply(nats_, msg, suggest_role_commands_response{});
405 BOOST_LOG_SEV(role_handler_lg(), debug)
406 <<
"Completed " << msg.
subject;
407 reply(nats_, msg, suggest_role_commands_response{
408 .commands = std::move(results)});
409 }
catch (
const std::exception& e) {
410 BOOST_LOG_SEV(role_handler_lg(), error)
411 << msg.
subject <<
" failed: " << e.what();
412 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