#ifndef OATPP_AUTHKIT_REPO_SCOPE_GUARD_REPOSITORY_HPP #define OATPP_AUTHKIT_REPO_SCOPE_GUARD_REPOSITORY_HPP #include "oatpp-authkit/repo/Repository.hpp" #include "oatpp-authkit/repo/ActorContext.hpp" #include "oatpp-authkit/repo/SchemaContract.hpp" #include "oatpp/core/Types.hpp" #include #include #include #include namespace oatpp_authkit::repo { /** * @brief Thrown when the scope guard predicate denies an operation. * * Catchers (typically the controller layer) translate this into the * appropriate HTTP error — 403 Forbidden in fewo-webapp's case. The * decorator stays library-portable by throwing a plain exception rather * than coupling to oatpp's `OatppException` hierarchy. */ class ScopeDeniedException : public std::runtime_error { public: using std::runtime_error::runtime_error; }; /** * @brief Decorator that gates every repository operation on a predicate. * * Generic — knows nothing about "property" / "tenant" / any consumer- * specific scope concept. The predicate decides; this class just calls it. * * @section semantics Per-method behaviour * * - `findByEntityId(id)`: load from inner; if non-null and predicate * denies, throw `ScopeDeniedException`. (Information-leak vs. clean * error tradeoff: throwing is the safer default — callers that want to * silently 404 instead can catch and translate.) * - `list()`: load from inner; filter out rows the predicate denies. * - `save(dto)`: predicate evaluated on the incoming dto; deny ⇒ throw. * - `softDelete(id)`: load from inner; if denied, throw; otherwise delegate. * * The actor is provided via a constructor-injected accessor so a single * `ScopeGuardRepository` instance can serve many requests with different * actors (typically the accessor reads from the per-request authenticated * principal — fewo-webapp's `AuthInterceptor` populates one). */ template class ScopeGuardRepository : public Repository { public: using Predicate = std::function&)>; using ActorAccess = std::function; /// Declarative schema contribution (authkit#14, D-replace). /// ScopeGuard touches no schema — empty contributions exposed so it /// composes cleanly into `SchemaBuilder<…>` parameter packs. inline static constexpr DecoratorSchema kSchema = { "ScopeGuardRepository", nullptr, 0, nullptr, 0, nullptr, 0, }; ScopeGuardRepository(std::shared_ptr> inner, Predicate isAllowed, ActorAccess currentActor) : m_inner(std::move(inner)) , m_isAllowed(std::move(isAllowed)) , m_currentActor(std::move(currentActor)) {} oatpp::Object findByEntityId(const oatpp::String& entityId) override { auto row = m_inner->findByEntityId(entityId); if (!row) return row; if (!m_isAllowed(m_currentActor(), row)) { throw ScopeDeniedException("scope guard denied findByEntityId"); } return row; } oatpp::Vector> list() override { auto inAll = m_inner->list(); auto out = oatpp::Vector>::createShared(); const ActorContext actor = m_currentActor(); for (auto& row : *inAll) { if (m_isAllowed(actor, row)) out->push_back(row); } return out; } void save(const oatpp::Object& dto) override { if (!m_isAllowed(m_currentActor(), dto)) { throw ScopeDeniedException("scope guard denied save"); } m_inner->save(dto); } void softDelete(const oatpp::String& entityId) override { auto row = m_inner->findByEntityId(entityId); if (!row) return; // Nothing to delete; matches Repository::softDelete being a no-op for unknown ids. if (!m_isAllowed(m_currentActor(), row)) { throw ScopeDeniedException("scope guard denied softDelete"); } m_inner->softDelete(entityId); } private: std::shared_ptr> m_inner; Predicate m_isAllowed; ActorAccess m_currentActor; }; } // namespace oatpp_authkit::repo #endif