view ViewerPlugin/Annotations/IAuthenticatedUser.cpp @ 613:617f25e606d6 default tip

back to mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 10 Sep 2026 10:35:58 +0200
parents 5b30b8d2b9e8
children
line wrap: on
line source

/**
 * Orthanc - A Lightweight, RESTful DICOM Store
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-2023 Osimis S.A., Belgium
 * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
 * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM 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 "../../Framework/PrecompiledHeadersWSI.h"
#include "IAuthenticatedUser.h"

#include "../ViewerConfiguration.h"

#include <Compatibility.h>
#include <OrthancException.h>
#include <SerializationToolbox.h>
#include <Toolbox.h>

#include "../../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"

#include <cassert>
#include <json/reader.h>


namespace OrthancWSI
{
  namespace
  {
    // If Orthanc runs without an authentication plugin
    class RootUser : public IAuthenticatedUser
    {
    public:
      virtual UserId GetAnnotatingId() const ORTHANC_OVERRIDE
      {
        return UserId(UserId::Type_Root);
      }

      virtual std::string Format() const ORTHANC_OVERRIDE
      {
        return "(root)";
      }

      virtual ProjectRole GetRoleInProject(const std::string& projectId) const ORTHANC_OVERRIDE
      {
        return ProjectRole_Instructor;
      }
    };


    class GuestUser : public IAuthenticatedUser
    {
    public:
      virtual UserId GetAnnotatingId() const ORTHANC_OVERRIDE
      {
        // Anonymous users cannot save annotations
        throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess);
      }

      virtual std::string Format() const ORTHANC_OVERRIDE
      {
        return "(guest)";
      }

      virtual ProjectRole GetRoleInProject(const std::string& projectId) const ORTHANC_OVERRIDE
      {
        return ProjectRole_Guest;
      }
    };


    class EducationPluginUser : public IAuthenticatedUser
    {
    public:
      enum EducationRole
      {
        EducationRole_Administrator,
        EducationRole_Standard,
        EducationRole_Guest
      };

    private:
      std::string            id_;
      EducationRole          role_;
      std::set<std::string>  instructorOfProjects_;
      std::set<std::string>  learnerOfProjects_;

    public:
      explicit EducationPluginUser(const Json::Value& authentication)
      {
        assert(Orthanc::SerializationToolbox::ReadString(authentication, "source") == "orthanc-education");

        id_ = Orthanc::SerializationToolbox::ReadString(authentication, "id");

        const std::string role = Orthanc::SerializationToolbox::ReadString(authentication, "role");

        if (role == "admin")
        {
          role_ = EducationRole_Administrator;
        }
        else if (role == "standard")
        {
          role_ = EducationRole_Standard;
        }
        else if (role == "guest")
        {
          role_ = EducationRole_Guest;
        }
        else
        {
          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
        }

        Orthanc::SerializationToolbox::ReadSetOfStrings(instructorOfProjects_, authentication, "instructor_of");
        Orthanc::SerializationToolbox::ReadSetOfStrings(learnerOfProjects_, authentication, "learner_of");
      }

      virtual UserId GetAnnotatingId() const ORTHANC_OVERRIDE
      {
        switch (role_)
        {
          case EducationRole_Administrator:
            return UserId(UserId::Type_Root);

          case EducationRole_Standard:
            return UserId(UserId::Type_Standard, id_);

          case EducationRole_Guest:
            // Anonymous users cannot save annotations
            throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess);

          default:
            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
        }
      }

      virtual std::string Format() const ORTHANC_OVERRIDE
      {
        return id_;
      }

      virtual ProjectRole GetRoleInProject(const std::string& projectId) const ORTHANC_OVERRIDE
      {
        switch (role_)
        {
          case EducationRole_Administrator:
            return ProjectRole_Instructor;

          case EducationRole_Standard:
            if (instructorOfProjects_.find(projectId) != instructorOfProjects_.end())
            {
              return ProjectRole_Instructor;
            }
            else if (learnerOfProjects_.find(projectId) != learnerOfProjects_.end())
            {
              return ProjectRole_Learner;
            }
            else
            {
              return ProjectRole_Guest;
            }

          case EducationRole_Guest:
            return ProjectRole_Guest;

          default:
            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
        }
      }
    };


    class GenericStandardUser : public IAuthenticatedUser
    {
    private:
      ProjectRole  role_;
      std::string  username_;

    public:
      GenericStandardUser(ProjectRole role,
                          const std::string& username) :
        role_(role),
        username_(username)
      {
      }

      virtual UserId GetAnnotatingId() const ORTHANC_OVERRIDE
      {
        return UserId(UserId::Type_Standard, username_);
      }

      virtual std::string Format() const ORTHANC_OVERRIDE
      {
        return username_;
      }

      virtual ProjectRole GetRoleInProject(const std::string& projectId) const ORTHANC_OVERRIDE
      {
        return role_;
      }
    };
  }


  static IAuthenticatedUser* FromRegisteredUsers(const OrthancPluginHttpRequest* request)
  {
    for (uint32_t i = 0; i < request->headersCount; i++)
    {
      if (std::string(request->headersKeys[i]) == "authorization")
      {
        const std::string value(request->headersValues[i]);

        std::vector<std::string> tokens;
        Orthanc::Toolbox::TokenizeString(tokens, value, ' ');

        if (tokens.size() == 2 &&
            tokens[0] == "Basic")
        {
          std::string decoded;
          Orthanc::Toolbox::DecodeBase64(decoded, tokens[1]);

          Orthanc::Toolbox::TokenizeString(tokens, decoded, ':');
          if (!tokens.empty() &&
              !tokens[0].empty())
          {
            return new GenericStandardUser(ProjectRole_Instructor, tokens[0]);
          }
        }
      }
    }

    throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess,
                                    "Forbidden access, HTTP basic authentication is missing");
  }


  static IAuthenticatedUser* FromHttpHeader(const OrthancPluginHttpRequest* request)
  {
    const std::string& header = ViewerConfiguration::GetInstance().GetAuthenticationHttpHeader();

    for (uint32_t i = 0; i < request->headersCount; i++)
    {
      if (std::string(request->headersKeys[i]) == header)
      {
        const std::string user(request->headersValues[i]);

        if (user.empty())
        {
          return new GuestUser;
        }
        else if (ViewerConfiguration::GetInstance().IsInstructor(user))
        {
          return new GenericStandardUser(ProjectRole_Instructor, user);
        }
        else
        {
          return new GenericStandardUser(ProjectRole_Learner, user);
        }
      }
    }

    throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess,
                                    "Forbidden access, as HTTP header \"" + header + "\" is not set by your proxy");
  }


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 9)
  static IAuthenticatedUser* FromPlugin(const OrthancPluginHttpRequest* request)
  {
    if (request->authenticationPayloadSize == 0)
    {
      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "No authentication plugin is properly installed");
    }
    else
    {
      const char* payload = reinterpret_cast<const char*>(request->authenticationPayload);

      // We use "Json::Reader" as "Orthanc::ReadJson()" would write an
      // error log if the authentication payload is not a JSON string
      Json::Reader reader;

      Json::Value authentication;
      if (reader.parse(payload, payload + request->authenticationPayloadSize, authentication, false))
      {
        const std::string source = Orthanc::SerializationToolbox::ReadString(authentication, "source", "(none)");

        if (source == "orthanc-education")  // This necessitate education plugin 1.2
        {
          return new EducationPluginUser(authentication);
        }
        else
        {
          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "Unknown authentication plugin: " + source);
        }
      }

      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "Unknown authentication plugin");
    }
  }
#endif



  IAuthenticatedUser* IAuthenticatedUser::FromHttpRequest(const OrthancPluginHttpRequest* request)
  {
    switch (ViewerConfiguration::GetInstance().GetAuthenticationSource())
    {
      case AuthenticationSource_None:
        // No authentication is available, use the root user of Orthanc
        return new RootUser;

      case AuthenticationSource_RegisteredUsers:
        return FromRegisteredUsers(request);

      case AuthenticationSource_HttpHeader:
        return FromHttpHeader(request);

      case AuthenticationSource_Plugin:
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 9)
        return FromPlugin(request);
#else
        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
#endif

      default:
        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
    }
  }
}