Mercurial > hg > orthanc-education
view Sources/LTI/LTIContext.cpp @ 77:80b663d5f8fe default tip
replaced boost::math::iround() by Orthanc::Math::llround()
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Tue, 27 Jan 2026 17:05:03 +0100 |
| parents | 0f8c46d755e2 |
| children |
line wrap: on
line source
/** * SPDX-FileCopyrightText: 2024-2026 Sebastien Jodogne, EPL UCLouvain, Belgium * SPDX-License-Identifier: AGPL-3.0-or-later */ /** * Orthanc for Education * Copyright (C) 2024-2026 Sebastien Jodogne, EPL UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ #include "LTIContext.h" #include "../EducationEnumerations.h" #include "../HttpToolbox.h" #include <cassert> static const char* const COOKIE_OIDC_SESSION = "orthanc-education-oidc"; class LTIContext::Session : public boost::noncopyable { private: std::string state_; std::string nonce_; public: Session() : state_(Orthanc::Toolbox::GenerateUuid()), nonce_(Orthanc::Toolbox::GenerateUuid()) { } const std::string& GetState() const { return state_; } const std::string& GetNonce() const { return nonce_; } }; bool LTIContext::LookupSessionUnsafe(std::string& sessionId, std::string& state, std::string& nonce, const std::list<HttpToolbox::Cookie>& cookies) { // The same cookie might be present multiple times, hence the loop for (std::list<HttpToolbox::Cookie>::const_iterator cookie = cookies.begin(); cookie != cookies.end(); ++cookie) { if (cookie->GetKey() == COOKIE_OIDC_SESSION) { sessionId = cookie->GetValue(); Session* session = NULL; if (sessions_.Contains(sessionId, session)) { if (session == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } else { state = session->GetState(); nonce = session->GetNonce(); sessions_.MakeMostRecent(sessionId); return true; } } } } return false; } void LTIContext::OpenSessionUnsafe(std::string& sessionId, std::string& state, std::string& nonce) { { std::unique_ptr<Session> session(new Session); sessionId = Orthanc::Toolbox::GenerateUuid(); state = session->GetState(); nonce = session->GetNonce(); sessions_.Add(sessionId, session.release()); } if (sessions_.GetSize() > 1000 /* maximum number of active sessions */) { Session* session = NULL; std::string s = sessions_.RemoveOldest(session); assert(session != NULL); delete session; LOG(INFO) << "Closing old LTI session: " << s; } } LTIContext::~LTIContext() { while (!sessions_.IsEmpty()) { Session* session = NULL; sessions_.RemoveOldest(session); assert(session != NULL); delete session; } } void LTIContext::CreatePrivateKey() { boost::mutex::scoped_lock lock(mutex_); LOG(WARNING) << "Generating a private RSA key using OpenSSL"; keyId_ = Orthanc::Toolbox::GenerateUuid(); privateKey_.Generate(); LOG(INFO) << "Generation of the private RSA key is done"; publicKey_.LoadFromPrivate(privateKey_); } void LTIContext::LoadPrivateKey(const std::string& keyId, const std::string& pem) { if (keyId.empty() || pem.empty()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } else { keyId_ = keyId; privateKey_.Unserialize(pem); publicKey_.LoadFromPrivate(privateKey_); } } void LTIContext::ForgeJWT(std::string& jwt, const Json::Value& payload, unsigned int maxAge /* in seconds */) { if (payload.type() != Json::objectValue) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); } const int64_t now = time(NULL); Json::Value enriched = payload; enriched["iat"] = static_cast<Json::Value::Int64>(now); enriched["exp"] = static_cast<Json::Value::Int64>(now + maxAge); { boost::mutex::scoped_lock lock(mutex_); privateKey_.ForgeJWT(jwt, keyId_, enriched); } } bool LTIContext::VerifyJWT(const JWT& jwt) { boost::mutex::scoped_lock lock(mutex_); return jwt.Verify(publicKey_); } void LTIContext::EnterSession(OrthancPluginRestOutput* output, std::string& state, std::string& nonce, const std::string& cookieHeader, bool secureCookie) { std::list<HttpToolbox::Cookie> cookies; HttpToolbox::ParseCookies(cookies, cookieHeader); { boost::mutex::scoped_lock lock(mutex_); std::string sessionId; if (!LookupSessionUnsafe(sessionId, state, nonce, cookies)) { OpenSessionUnsafe(sessionId, state, nonce); HttpToolbox::SetCookie(output, COOKIE_OIDC_SESSION, sessionId, CookieSameSite_None, secureCookie); LOG(INFO) << "Opening new LTI session: " << sessionId; } else { LOG(INFO) << "Reusing old LTI session: " << sessionId; } } } void LTIContext::CloseSession(OrthancPluginRestOutput* output, bool secureCookie) { HttpToolbox::ClearCookie(output, COOKIE_OIDC_SESSION, CookieSameSite_None, secureCookie); } bool LTIContext::CheckSession(const std::string& cookieHeader, const std::string& expectedState) { std::list<HttpToolbox::Cookie> cookies; HttpToolbox::ParseCookies(cookies, cookieHeader); { boost::mutex::scoped_lock lock(mutex_); std::string sessionId, state, nonce; return (LookupSessionUnsafe(sessionId, state, nonce, cookies) && state == expectedState); } }
