Skip to content

Commit

Permalink
SAK-45616 Samigo add Proctorio capabilities to Sakai (sakaiproject#9454)
Browse files Browse the repository at this point in the history
* SAK-45616 add Proctorio capabilities via direct API

* SAK-45616 handle scenario where proctoring solution is down

* Proctorio remove persistence and caching so links are regen'ed every time

Co-authored-by: Brian Baillargeon <[email protected]>
  • Loading branch information
ottenhoff and bbailla2 authored Oct 5, 2021
1 parent b1dba46 commit 789b857
Show file tree
Hide file tree
Showing 30 changed files with 1,012 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3702,6 +3702,30 @@
# DEFAULT: true
# samigo.remove.drafts=false

#########################################
# SAMIGO SECURE DELIVERY (PROCTORING
#########################################

# Respondus will send a closed-source JAR that will need to be installed in lib/ dir
# samigo.secureDeliveryPlugins=/path/to/custom.jar

# Proctorio integration uses the Proctorio API
# To enable Proctorio:
# samigo.secureDeliveryPlugins=proctorio

# Proctorio can be enabled globally or on a per-site basis
# proctorio.enabled=always/site

# Proctorio account admin will provide a key, secret, and url
# proctorio.key=xxx
# proctorio.secret=yyy
# proctorio.url=https://xxxxxx.proctor.io/yyyyyy

# Proctorio includes a number of options. Institutional admins choose a default set of options that can be overridden with a site property called "proctorio"
# DEFAULT: recordvideo,linksonly
# proctorio.options=recordvideo, recordaudio, recordscreen, recordwebtraffic, recordroomstart, verifyvideo, verifyaudio, verifydesktop, verifyidauto, verifyidlive, verifysignature,
# fullscreenlenient, fullscreenmoderate, fullscreensevere, clipboard, notabs, linksonly, closetabs, onescreen, print, downloads, cache, rightclick, noreentry, agentreentry, calculatorbasic, calculatorsci, whiteboard

#########################################
# MEMBERSHIP TOOL
#########################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.sakaiproject.tool.assessment.data.ifc.assessment;

import java.util.Locale;
import java.util.Optional;

import javax.servlet.http.HttpServletRequest;

Expand Down Expand Up @@ -110,4 +111,36 @@ public interface SecureDeliveryModuleIfc {
* @return the plain text password
*/
public String decryptPassword( String password );

/**
* Returns an absolute URL to an alternative location to take the assessment.
* For example, a cloud proctoring company could use this URL to embed the Samigo assessment in an iframe.
* @param assessmentId
* @param uid
* @return
*/
default Optional<String> getAlternativeDeliveryUrl(Long assessmentId, String uid) {
return Optional.empty();
}

/**
* Returns an absolute URL to an alternative location to review the student's taking of the proctored assessment.
* @param assessmentId
* @param studentId (internal user id)
* @return
*/
default Optional<String> getInstructorReviewUrl(Long assessmentId, String studentId) {
return Optional.empty();
}

/**
* Returns whether the SecureDelivery service is implemented for this one assessment
* Some institutions may want to limit what assessments or sites are allowed to use the service.
* @param assessment
* @return
*/
default boolean isEnabled(Long assessmentId) {
return isEnabled();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.sakaiproject.tool.assessment.shared.api.assessment;

import java.util.Locale;
import java.util.Optional;
import java.util.SortedSet;

import javax.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -148,4 +149,16 @@ public enum Phase { ASSESSMENT_START, ASSESSMENT_FINISH, ASSESSMENT_REVIEW };
* @return the reference. null if the module is not avaliable or if the module rejected the context
*/
public SecureDeliveryModuleIfc getModuleReference( String moduleId, Object context );

public Optional<String> getAlternativeDeliveryUrl( String moduleId, Long assessmentId, String uid );

public Optional<String> getInstructorReviewUrl( String moduleId, Long assessmentId, String studentId );

public boolean isSecureDeliveryAvaliable( Long publishedAssessmentId );

/**
* Gets the name of the secure delivery service associated with the specified module
*/
public Optional<String> getSecureDeliveryServiceNameForModule(String moduleId, Locale locale);

}
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ anonymous_thank_you_message=Thank you. Your assessment has been submitted. Click
anonymous_quit_warning=Assessment Exit Warning
access_denied=Access Denied
access_denied_message=You do not have permission to access this assessment. Please contact your instructor if you have any questions.
secure_delivery_error_take_message=An error occurred while retrieving the link to take this assessment through {0}. Please contact your instructor.
secure_delivery_error_review_message=An error occurred while retrieving the link to review this assessment through {0}.
media_access_denied=Media Access Denied
media_access_denied_message=You do not have permission to access this media. If you are not currently logged in, please log in and try again. If you are logged in and believe you should be able to view this file, please contact your local support for help.
warning=WARNING
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ error=Error!
sent_email_confirmation=Your email has been sent. Please click the Close button to close this window.
sent_email_error=The system has encountered a problem while trying to send your email. Please contact your system administrator for more help.
email_warning=Warning: If you have more than one email window open at a time, the recipients and/or messages may get mixed up. Please email only one person at a time.
instructor_review=Instructor Review Link

# question types
q_aud=Audio Response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1786,15 +1786,14 @@ public SelectItem[] getSecureDeliverModuleSelections() {
SecureDeliveryServiceAPI secureDeliveryService = SamigoApiFactory.getInstance().getSecureDeliveryServiceAPI();
Set<RegisteredSecureDeliveryModuleIfc> modules = secureDeliveryService.getSecureDeliveryModules( new ResourceLoader().getLocale() );

SelectItem[] selections = new SelectItem[ modules.size() ];
int index = 0;
List<SelectItem> selections = new ArrayList<>();
for ( RegisteredSecureDeliveryModuleIfc module : modules ) {
if (!SecureDeliveryServiceAPI.NONE_ID.equals(module.getId()) && !module.isEnabled()) continue;

selections[index] = new SelectItem( module.getId(), module.getName() );
++index;
selections.add(new SelectItem( module.getId(), module.getName() ));
}

return selections;
return selections.toArray(new SelectItem[selections.size()]);
}

public void setCategoriesEnabled(boolean categoriesEnabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1698,15 +1698,14 @@ public SelectItem[] getSecureDeliverModuleSelections() {
SecureDeliveryServiceAPI secureDeliveryService = SamigoApiFactory.getInstance().getSecureDeliveryServiceAPI();
Set<RegisteredSecureDeliveryModuleIfc> modules = secureDeliveryService.getSecureDeliveryModules( new ResourceLoader().getLocale() );

SelectItem[] selections = new SelectItem[ modules.size() ];
int index = 0;
List<SelectItem> selections = new ArrayList<>();
for ( RegisteredSecureDeliveryModuleIfc module : modules ) {
if (!SecureDeliveryServiceAPI.NONE_ID.equals(module.getId()) && !module.isEnabled()) continue;

selections[index] = new SelectItem( module.getId(), module.getName() );
++index;
selections.add(new SelectItem( module.getId(), module.getName() ));
}

return selections;
return selections.toArray(new SelectItem[selections.size()]);
}

public void setExtendedTimes(List<ExtendedTime> extendedTimes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TimeZone;
Expand Down Expand Up @@ -105,6 +106,7 @@
import org.sakaiproject.tool.cover.ToolManager;
import org.sakaiproject.user.api.PreferencesService;
import org.sakaiproject.util.api.FormattedText;
import org.sakaiproject.util.ResourceLoader;

import lombok.Getter;
import lombok.Setter;
Expand Down Expand Up @@ -413,6 +415,8 @@ public class DeliveryBean implements Serializable {
@Getter @Setter
private String secureDeliveryHTMLFragment;

private PhaseStatus secureDeliveryStatus = null;

@Getter @Setter
private boolean isFromPrint;

Expand Down Expand Up @@ -787,12 +791,12 @@ private String submitForGrade(boolean isFromTimer, boolean submitFromTimeoutPopu
SecureDeliveryServiceAPI secureDelivery = SamigoApiFactory.getInstance().getSecureDeliveryServiceAPI();
if ( secureDelivery.isSecureDeliveryAvaliable() ) {
String moduleId = publishedAssessment.getAssessmentMetaDataByLabel( SecureDeliveryServiceAPI.MODULE_KEY );
if ( moduleId != null && ! SecureDeliveryServiceAPI.NONE_ID.equals( moduleId ) ) {
if (moduleExists(moduleId)) {

HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
PhaseStatus status = secureDelivery.validatePhase(moduleId, Phase.ASSESSMENT_FINISH, publishedAssessment, request );
secureDeliveryStatus = secureDelivery.validatePhase(moduleId, Phase.ASSESSMENT_FINISH, publishedAssessment, request );
setSecureDeliveryHTMLFragment(
secureDelivery.getHTMLFragment(moduleId, publishedAssessment, request, Phase.ASSESSMENT_FINISH, status, locale) );
secureDelivery.getHTMLFragment(moduleId, publishedAssessment, request, Phase.ASSESSMENT_FINISH, secureDeliveryStatus, locale) );
}
}

Expand Down Expand Up @@ -1316,16 +1320,16 @@ public String validate() {
setSecureDeliveryHTMLFragment( "" );
setBlockDelivery( false );
SecureDeliveryServiceAPI secureDelivery = SamigoApiFactory.getInstance().getSecureDeliveryServiceAPI();
if ( "takeAssessment".equals(results) && secureDelivery.isSecureDeliveryAvaliable() ) {
if ( "takeAssessment".equals(results) && secureDelivery.isSecureDeliveryAvaliable(publishedAssessment.getPublishedAssessmentId()) ) {

String moduleId = publishedAssessment.getAssessmentMetaDataByLabel( SecureDeliveryServiceAPI.MODULE_KEY );
if ( moduleId != null && ! SecureDeliveryServiceAPI.NONE_ID.equals( moduleId ) ) {
if (moduleExists(moduleId)) {
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
PhaseStatus status = secureDelivery.validatePhase(moduleId, Phase.ASSESSMENT_START, publishedAssessment, request );
secureDeliveryStatus = secureDelivery.validatePhase(moduleId, Phase.ASSESSMENT_START, publishedAssessment, request );
setSecureDeliveryHTMLFragment(
secureDelivery.getHTMLFragment(moduleId, publishedAssessment, request, Phase.ASSESSMENT_START, status, locale));
setBlockDelivery( PhaseStatus.FAILURE == status );
if ( PhaseStatus.SUCCESS == status ) {
secureDelivery.getHTMLFragment(moduleId, publishedAssessment, request, Phase.ASSESSMENT_START, secureDeliveryStatus, locale));
setBlockDelivery( PhaseStatus.FAILURE == secureDeliveryStatus );
if ( PhaseStatus.SUCCESS == secureDeliveryStatus ) {
results = "takeAssessment";
} else {
results = "secureDeliveryError";
Expand Down Expand Up @@ -1423,6 +1427,50 @@ public int getSubmissionStatus() {
}
}

/**
* Returns an appropriate error message if an error occurred with the SecureDeliveryService (E.g. if the remote proctoring service is down)
*/
public String getSecureDeliveryErrorMessage()
{
Phase phase;
String messageKey;
switch (actionMode)
{
case REVIEW_ASSESSMENT:
case GRADE_ASSESSMENT:
phase = Phase.ASSESSMENT_REVIEW;
messageKey = "secure_delivery_error_review_message";
break;
default:
phase = Phase.ASSESSMENT_START;
messageKey = "secure_delivery_error_take_message";
}

SecureDeliveryServiceAPI secureDelivery = SamigoApiFactory.getInstance().getSecureDeliveryServiceAPI();
String moduleId = publishedAssessment.getAssessmentMetaDataByLabel(SecureDeliveryServiceAPI.MODULE_KEY);

if (moduleExists(moduleId))
{
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
if (PhaseStatus.FAILURE == secureDeliveryStatus || PhaseStatus.FAILURE == secureDelivery.validatePhase(moduleId, phase, publishedAssessment, request))
{
ResourceLoader rb = new ResourceLoader("org.sakaiproject.tool.assessment.bundle.DeliveryMessages");
Optional<String> moduleName = secureDelivery.getSecureDeliveryServiceNameForModule(moduleId, rb.getLocale());
if (moduleName.isPresent())
{
return rb.getFormattedMessage(messageKey, moduleName.get());
}
}
}
return "";
}

public boolean moduleExists(String moduleId)
{
return moduleId != null && !SecureDeliveryServiceAPI.NONE_ID.equals(moduleId);
}


public void updatEventLog(String errorMsg) {
EventLogService eventService = new EventLogService();
EventLogFacade eventLogFacade = new EventLogFacade();
Expand Down Expand Up @@ -1824,7 +1872,7 @@ public boolean getTimeExpired(){
return timeExpired;
}

private void removeTimedAssessmentFromQueue(){
public void removeTimedAssessmentFromQueue(){
if (adata==null) {
return;
}
Expand Down Expand Up @@ -2079,7 +2127,25 @@ public String checkBeforeProceed(boolean isSubmitForGrade, boolean isFromTimer,
if (isTimeRunning() && getTimeExpired() && !turnIntoTimedAssessment){
return "timeExpired";
}


// Check 10: see if SecureDelivery is okay with this
log.debug("check10-SecureDelivery");
if (isViaUrlLogin) {
SecureDeliveryServiceAPI secureDelivery = SamigoApiFactory.getInstance().getSecureDeliveryServiceAPI();
if ( secureDelivery.isSecureDeliveryAvaliable(publishedAssessment.getPublishedAssessmentId()) ) {
String moduleId = publishedAssessment.getAssessmentMetaDataByLabel( SecureDeliveryServiceAPI.MODULE_KEY );
if (moduleExists(moduleId)) {
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
secureDeliveryStatus = secureDelivery.validatePhase(moduleId, Phase.ASSESSMENT_START, publishedAssessment, request );
setBlockDelivery( PhaseStatus.FAILURE == secureDeliveryStatus );
setSecureDeliveryHTMLFragment(secureDelivery.getHTMLFragment(moduleId, publishedAssessment, request, Phase.ASSESSMENT_START, secureDeliveryStatus, locale));
if ( PhaseStatus.FAILURE == secureDeliveryStatus ) {
return "secureDeliveryError";
}
}
}
}

return "safeToProceed";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public class DeliveryBeanie
private int timeLimit_hour;
private int timeLimit_minute;
private String finalScore;
private String alternativeDeliveryUrl;
private boolean isAssessmentBeanie=false;

// display * and notes for multiple submissions
Expand Down Expand Up @@ -530,11 +531,22 @@ public String getFinalScore() {
public void setFinalScore(String finalScore) {
this.finalScore = finalScore;
}

public String getAlternativeDeliveryUrl() {
return alternativeDeliveryUrl;
}

public void setAlternativeDeliveryUrl(String url) {
this.alternativeDeliveryUrl = url;

}

public boolean getIsAssessmentBeanie() {
return isAssessmentBeanie;
}

public void setIsAssessmentBeanie(boolean isAssessmentBeanie) {
this.isAssessmentBeanie = isAssessmentBeanie;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentMetaDataIfc;
import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentAccessControlIfc;
import org.sakaiproject.tool.assessment.data.ifc.assessment.PublishedAssessmentIfc;
import org.sakaiproject.tool.assessment.shared.api.assessment.SecureDeliveryServiceAPI;

import java.io.Serializable;
import java.util.Date;
Expand Down Expand Up @@ -60,6 +61,7 @@ public class SettingsDeliveryBean implements Serializable
private String background;
private String itemNumbering;
private String displayScoreDuringAssessments;
private String secureDeliveryModule;

/**
* Maximum number of attemtps allowed.
Expand Down Expand Up @@ -341,6 +343,15 @@ public String getDisplayScoreDuringAssessments()
public void setDisplayScoreDuringAssessments(String displayScoreDuringAssessments){
this.displayScoreDuringAssessments = displayScoreDuringAssessments;
}

public String getSecureDeliveryModule()
{
return secureDeliveryModule;
}

public void setSecureDeliveryModule(String secureDeliveryModule){
this.secureDeliveryModule = secureDeliveryModule;
}

public void setAssessmentAccessControl(PublishedAssessmentIfc pubAssessment){

Expand Down Expand Up @@ -385,6 +396,8 @@ public void setAssessmentAccessControl(PublishedAssessmentIfc pubAssessment){
setBgcolor(data.getEntry());
else if (data.getLabel().equals(AssessmentMetaDataIfc.BGIMAGE))
setBackground(data.getEntry());
else if (data.getLabel().equals(SecureDeliveryServiceAPI.MODULE_KEY))
setSecureDeliveryModule(data.getEntry());
}
}

Expand Down
Loading

0 comments on commit 789b857

Please sign in to comment.