# HG changeset patch # User Sebastien Jodogne # Date 1788796860 -7200 # Node ID 11f7950177634e805bd1b1486fe23a1130d5b903 # Parent 008409bc997863eeec878b6a94bceb03364a6356 implemented sharing rules diff -r 008409bc9978 -r 11f795017763 ViewerPlugin/Annotations/AnnotationsRestApi.cpp --- a/ViewerPlugin/Annotations/AnnotationsRestApi.cpp Mon Sep 07 11:34:02 2026 +0200 +++ b/ViewerPlugin/Annotations/AnnotationsRestApi.cpp Mon Sep 07 18:01:00 2026 +0200 @@ -151,6 +151,9 @@ const char* url, const OrthancPluginHttpRequest* request) { + static const char* const KEY_IS_LEARNER = "is_learner"; + static const char* const KEY_IS_INSTRUCTOR = "is_instructor"; + if (ProtectPostRequest(output, request)) { AnnotationsCommandContext context(request); @@ -163,17 +166,20 @@ answer["enabled"] = ViewerConfiguration::GetInstance().AreAnnotationsEnabled(); answer["sharing"] = (ViewerConfiguration::GetInstance().AreAnnotationsEnabled() && ViewerConfiguration::GetInstance().IsAnnotationsSharingEnabled()); + answer["learner_to_learner_sharing"] = ViewerConfiguration::GetInstance().IsLearnerToLearnerSharingEnabled(); answer["user"] = context.GetUser().Format(); std::string role; switch (context.GetRole()) { case ProjectRole_Learner: - role = "learner"; + answer[KEY_IS_LEARNER] = true; + answer[KEY_IS_INSTRUCTOR] = false; break; case ProjectRole_Instructor: - role = "instructor"; + answer[KEY_IS_LEARNER] = false; + answer[KEY_IS_INSTRUCTOR] = true; break; default: @@ -335,7 +341,11 @@ const std::string query = context.GetBodyString("query"); std::set users; - context.GetWorkspace().SearchActiveUsers(users, query); + + { + std::unique_ptr reader(context.CreateUserReader()); + reader->SearchActiveUsers(users, query); + } Json::Value answer = Json::arrayValue; diff -r 008409bc9978 -r 11f795017763 ViewerPlugin/Annotations/AnnotationsWorkspace.cpp --- a/ViewerPlugin/Annotations/AnnotationsWorkspace.cpp Mon Sep 07 11:34:02 2026 +0200 +++ b/ViewerPlugin/Annotations/AnnotationsWorkspace.cpp Mon Sep 07 18:01:00 2026 +0200 @@ -24,6 +24,7 @@ #include "../../Framework/PrecompiledHeadersWSI.h" #include "AnnotationsWorkspace.h" +#include "../ViewerConfiguration.h" #include "../ViewerToolbox.h" #include @@ -337,29 +338,6 @@ } - void AnnotationsWorkspace::SearchActiveUsers(std::set& target, - const std::string& query) - { - Orthanc::ReaderWriterLock::ReadLock lock(mutex_); - - target.clear(); - - const boost::regex re(query); - - PersistentInfo::ActiveUsersIterator iterator(*persistentInfo_); - - while (!iterator.IsDone()) - { - if (boost::regex_search(iterator.GetUser().GetName(), re)) - { - target.insert(iterator.GetUser()); - } - - iterator.Next(); - } - } - - AnnotationsWorkspace::UserReader::UserReader(AnnotationsWorkspace& that, const UserId& userId, ProjectRole userRole) : @@ -463,6 +441,62 @@ } + void AnnotationsWorkspace::UserReader::SearchActiveUsers(std::set& target, + const std::string& query) const + { + target.clear(); + + const boost::regex re(query); + + PersistentInfo::ActiveUsersIterator iterator(*that_.persistentInfo_); + + while (!iterator.IsDone()) + { + if (boost::regex_search(iterator.GetUser().GetName(), re)) + { + bool add = false; + + switch (userRole_) + { + case ProjectRole_Instructor: + add = true; + break; + + case ProjectRole_Learner: + switch (iterator.GetRole()) // Consider the role of the other user + { + case ProjectRole_Instructor: + // Learners can always share with instructors + add = true; + break; + + case ProjectRole_Learner: + add = ViewerConfiguration::GetInstance().IsLearnerToLearnerSharingEnabled(); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + break; + + case ProjectRole_Guest: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess); + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (add) + { + target.insert(iterator.GetUser()); + } + } + + iterator.Next(); + } + } + + void AnnotationsWorkspace::UserReader::ListImportedLayers(std::set& authors, std::set& layerIds) const { diff -r 008409bc9978 -r 11f795017763 ViewerPlugin/Annotations/AnnotationsWorkspace.h --- a/ViewerPlugin/Annotations/AnnotationsWorkspace.h Mon Sep 07 11:34:02 2026 +0200 +++ b/ViewerPlugin/Annotations/AnnotationsWorkspace.h Mon Sep 07 18:01:00 2026 +0200 @@ -73,9 +73,6 @@ return projectInformation_.GetDescription(); } - void SearchActiveUsers(std::set& target, - const std::string& query); - class UserReader : public boost::noncopyable { @@ -105,6 +102,9 @@ void ListImportedLayers(std::set& authors, std::set& layerIds) const; + + void SearchActiveUsers(std::set& target, + const std::string& query) const; }; diff -r 008409bc9978 -r 11f795017763 ViewerPlugin/Annotations/UserLayer.cpp --- a/ViewerPlugin/Annotations/UserLayer.cpp Mon Sep 07 11:34:02 2026 +0200 +++ b/ViewerPlugin/Annotations/UserLayer.cpp Mon Sep 07 18:01:00 2026 +0200 @@ -24,6 +24,8 @@ #include "../../Framework/PrecompiledHeadersWSI.h" #include "UserLayer.h" +#include "../ViewerConfiguration.h" + #include #include #include @@ -79,12 +81,83 @@ const UserId& viewerId, ProjectRole viewerRole) const { - assert(viewerId.GetType() == UserId::Type_Root || - viewerId.GetType() == UserId::Type_Standard); + /** + + Instructors can see: + + - All layers tagged "publicly shared with instructors" + (i.e. public), created by anyone (instructors or learners). + + - Any layer explicitly shared with them, by anyone. + + Learners can see: + + - All layers tagged public that were created by instructors + (this is true "class-wide public" for instructor content). + + - Any instructor layer explicitly shared with them. + + - Any learner layer explicitly shared with them by name, only + if learner-to-learner sharing is enabled (cf. configuration + option "EnableLearnerToLearnerSharing"). + + Note 1: Learner layers tagged "public" are visible only to + instructors, never to other learners, regardless of the + learner-to-learner sharing configuration. This is a deliberate + asymmetry: for a learner, "public" means "submitted/visible to + instructors," not "visible to the class." This prevents one + learner's work from becoming broadcast to the whole cohort, + while still allowing small, named-group collaboration (e.g., + project teams) through explicit sharing. + + Note 2: Learner-to-learner sharing (configuration option) + governs only the explicit-share-list channel between + learners. It has no effect on instructor visibility and no + effect on the behavior of the "public" tag (public learner + layers are never learner-visible whether this option is "true" + or "false"). + + **/ - return (isPublic_ || - viewerId.GetType() == UserId::Type_Root || - sharedWith_.find(viewerId) != sharedWith_.end()); + if (viewerId.GetType() != UserId::Type_Standard) + { + return false; + } + + const bool explicitlyShared = sharedWith_.find(viewerId) != sharedWith_.end(); + + switch (authorRole) + { + case ProjectRole_Instructor: + // Instructor layers: "public" truly means public to everyone, + // and explicit sharing is unconditional + return isPublic_ || explicitlyShared; + + case ProjectRole_Learner: + switch (viewerRole) + { + case ProjectRole_Instructor: + // Instructors see public learner layers, and anything shared with them + return isPublic_ || explicitlyShared; + + case ProjectRole_Learner: + // Learner viewing another learner's layer: "public" never applies, + // explicit sharing is gated by the configuration switch. + return explicitlyShared && ViewerConfiguration::GetInstance().IsLearnerToLearnerSharingEnabled(); + + case ProjectRole_Guest: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess); + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + case ProjectRole_Guest: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess); + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } } diff -r 008409bc9978 -r 11f795017763 ViewerPlugin/ViewerConfiguration.cpp --- a/ViewerPlugin/ViewerConfiguration.cpp Mon Sep 07 11:34:02 2026 +0200 +++ b/ViewerPlugin/ViewerConfiguration.cpp Mon Sep 07 18:01:00 2026 +0200 @@ -230,4 +230,10 @@ return instructors_.find(username) != instructors_.end(); } } + + + bool ViewerConfiguration::IsLearnerToLearnerSharingEnabled() const + { + return wsiConfiguration_.GetBooleanValue("EnableLearnerToLearnerSharing", false); + } } diff -r 008409bc9978 -r 11f795017763 ViewerPlugin/ViewerConfiguration.h --- a/ViewerPlugin/ViewerConfiguration.h Mon Sep 07 11:34:02 2026 +0200 +++ b/ViewerPlugin/ViewerConfiguration.h Mon Sep 07 18:01:00 2026 +0200 @@ -72,5 +72,7 @@ bool IsAnnotationsSharingEnabled() const; bool IsInstructor(const std::string& username) const; + + bool IsLearnerToLearnerSharingEnabled() const; }; } diff -r 008409bc9978 -r 11f795017763 ViewerPlugin/WebApplication/viewer.html --- a/ViewerPlugin/WebApplication/viewer.html Mon Sep 07 11:34:02 2026 +0200 +++ b/ViewerPlugin/WebApplication/viewer.html Mon Sep 07 18:01:00 2026 +0200 @@ -541,10 +541,16 @@
- +
- +
No users added
- +