diff --git a/bbb-lti/.asscache b/bbb-lti/.asscache deleted file mode 100644 index be6deceb5e9795140859e536f78de8fe794cf1ef..0000000000000000000000000000000000000000 Binary files a/bbb-lti/.asscache and /dev/null differ diff --git a/bbb-lti/.gitignore b/bbb-lti/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b1bf7c47d2acad6cf5cb5bab1e95b7914a7e881f --- /dev/null +++ b/bbb-lti/.gitignore @@ -0,0 +1 @@ +.asscache \ No newline at end of file diff --git a/bbb-lti/application.properties b/bbb-lti/application.properties index 68fe1dc6a3086a523d0d680d0a2aa94498698311..a00806dd9e3917149f9cd48666f6b864737f9235 100644 --- a/bbb-lti/application.properties +++ b/bbb-lti/application.properties @@ -2,4 +2,5 @@ #Fri Aug 19 19:12:11 UTC 2016 app.grails.version=2.5.2 app.name=lti -app.version=0.3 +app.servlet.version=3.0 +app.version=0.4 diff --git a/bbb-lti/grails-app/conf/BuildConfig.groovy b/bbb-lti/grails-app/conf/BuildConfig.groovy index 0bf647fec3f6cb2567a104182888511ffe2e6c43..4a84c2785ab79613b455443f1f6ee09cdabe83cd 100644 --- a/bbb-lti/grails-app/conf/BuildConfig.groovy +++ b/bbb-lti/grails-app/conf/BuildConfig.groovy @@ -1,4 +1,4 @@ -/* +/* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. -*/ +*/ grails.servlet.version = "3.0" // Change depending on target container compliance (2.5 or 3.0) grails.project.class.dir = "target/classes" @@ -65,6 +65,7 @@ grails.project.dependency.resolution = { } dependencies { + compile 'org.json:json:20171018' // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes e.g. // runtime 'mysql:mysql-connector-java:5.1.29' // runtime 'org.postgresql:postgresql:9.3-1101-jdbc41' diff --git a/bbb-lti/grails-app/conf/Config.groovy b/bbb-lti/grails-app/conf/Config.groovy index 40ca78588a826c528ffc3ddcb9782db2082841fb..966ea2329ee7c256de724ec2e5bb1e5d0a0aba8f 100644 --- a/bbb-lti/grails-app/conf/Config.groovy +++ b/bbb-lti/grails-app/conf/Config.groovy @@ -1,4 +1,4 @@ -/* +/* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. -*/ +*/ // locations to search for config files that get merged into the main config; // config files can be ConfigSlurper scripts, Java properties files, or classes @@ -103,6 +103,9 @@ grails.hibernate.pass.readonly = false // configure passing read-only to OSIV session by default, requires "singleSession = false" OSIV mode grails.hibernate.osiv.readonly = false +// Enable hot reloading for production environments +grails.gsp.enable.reload=true + environments { development { grails.logging.jul.usebridge = true diff --git a/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy b/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy index 600218fedfb3fb3544939bf3798492c7fb3d19d5..b868a2b564c5cab3425ed9f20a5345b9b8095bd2 100644 --- a/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy +++ b/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy @@ -48,125 +48,103 @@ class ToolController { def index = { log.debug CONTROLLER_NAME + "#index" - if( ltiService.consumerMap == null) ltiService.initConsumerMap() - + if (ltiService.consumerMap == null) { + ltiService.initConsumerMap() + } setLocalization(params) - params.put(REQUEST_METHOD, request.getMethod().toUpperCase()) ltiService.logParameters(params) - - if( request.post ){ - def scheme = request.isSecure()? "https": "http" - def endPoint = scheme + "://" + ltiService.endPoint + "/" + grailsApplication.metadata['app.name'] + "/" + params.get("controller") + (params.get("format") != null? "." + params.get("format"): "") - log.info "endPoint: " + endPoint - Map<String, String> result = new HashMap<String, String>() - ArrayList<String> missingParams = new ArrayList<String>() - - if (hasAllRequiredParams(params, missingParams)) { - def sanitizedParams = sanitizePrametersForBaseString(params) - def consumer = ltiService.getConsumer(params.get(Parameter.CONSUMER_ID)) - if ( !ltiService.hasRestrictedAccess() || consumer != null) { - if (ltiService.hasRestrictedAccess() ) { - log.debug "Found consumer with key " + consumer.get("key") //+ " and sharedSecret " + consumer.get("secret") - } - - if (!ltiService.hasRestrictedAccess() || checkValidSignature(params.get(REQUEST_METHOD), endPoint, consumer.get("secret"), sanitizedParams, params.get(Parameter.OAUTH_SIGNATURE))) { - if (!ltiService.hasRestrictedAccess() ) { - log.debug "Access not restricted, valid signature is not required." - } else { - log.debug "The message has a valid signature." - } - - def mode = params.containsKey(Parameter.CUSTOM_MODE)? params.get(Parameter.CUSTOM_MODE): ltiService.mode - if( !"extended".equals(mode) ) { - log.debug "LTI service running in simple mode." - result = doJoinMeeting(params) - } else { - log.debug "LTI service running in extended mode." - if ( !Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) && !ltiService.allRecordedByDefault() ) { - log.debug "Parameter custom_record was not sent; immediately redirecting to BBB session!" - result = doJoinMeeting(params) - } - } - - } else { - log.debug "The message has NOT a valid signature." - result.put("resultMessageKey", "InvalidSignature") - result.put("resultMessage", "Invalid signature (" + params.get(Parameter.OAUTH_SIGNATURE) + ").") - } - - } else { - result.put("resultMessageKey", "ConsumerNotFound") - result.put("resultMessage", "Consumer with id = " + params.get(Parameter.CONSUMER_ID) + " was not found.") - } - - } else { - String missingStr = "" - for(String str:missingParams) { - missingStr += str + ", "; - } - result.put("resultMessageKey", "MissingRequiredParameter") - result.put("resultMessage", "Missing parameters [$missingStr]") + // On get requests render the common cartridge. + if (request.get) { + render(text: getCartridgeXML(), contentType: "text/xml", encoding: "UTF-8") + return + } + // On post request proceed with the launch. + def endPoint = ltiService.getScheme(request) + "://" + ltiService.endPoint + "/" + grailsApplication.metadata['app.name'] + "/" + params.get("controller") + (params.get("format") != null ? "." + params.get("format") : "") + log.info "endPoint: " + endPoint + ArrayList<String> missingParams = new ArrayList<String>() + + if (!hasAllRequiredParams(params, missingParams)) { + String missingStr = "" + for (String str:missingParams) { + missingStr += str + ", "; } + return renderError("MissingRequiredParameter", "Missing parameters [$missingStr]") + } - if( result.containsKey("resultMessageKey") ) { - log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']" - render(view: "error", model: ['resultMessageKey': result.get("resultMessageKey"), 'resultMessage': result.get("resultMessage")]) - - } else { - session["params"] = params - render(view: "index", model: ['params': params, 'recordingList': getSanitizedRecordings(params), 'ismoderator': bigbluebuttonService.isModerator(params)]) + def sanitizedParams = sanitizePrametersForBaseString(params) + def consumer = ltiService.getConsumer(params.get(Parameter.CONSUMER_ID)) + if (ltiService.hasRestrictedAccess()) { + if (consumer == null) { + return renderError("ConsumerNotFound", "Consumer with id = " + params.get(Parameter.CONSUMER_ID) + " was not found.") } + log.debug "Found consumer with key " + consumer.get("key") //+ " and sharedSecret " + consumer.get("secret") + } + def validSignature = checkValidSignature(params.get(REQUEST_METHOD), endPoint, consumer.get("secret"), sanitizedParams, params.get(Parameter.OAUTH_SIGNATURE)) + if (ltiService.hasRestrictedAccess()) { + if (!validSignature) { + log.debug "The message has NOT a valid signature." + return renderError("InvalidSignature", "Invalid signature (" + params.get(Parameter.OAUTH_SIGNATURE) + ").") + } + log.debug "The message has a valid signature." } else { - render(text: getCartridgeXML(), contentType: "text/xml", encoding: "UTF-8") + log.debug "Access not restricted, valid signature is not required." + } + def mode = params.containsKey(Parameter.CUSTOM_MODE)? params.get(Parameter.CUSTOM_MODE): ltiService.mode + if (!"extended".equals(mode)) { + log.debug "LTI service running in simple mode." + def result = doJoinMeeting(params) + return } + log.debug "LTI service running in extended mode." + if (!Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) && !ltiService.allRecordedByDefault()) { + log.debug "Parameter custom_record was not sent; immediately redirecting to BBB session!" + def result = doJoinMeeting(params) + return + } + session["params"] = params + render(view: "index", model: ['params': params, 'recordingList': getSanitizedRecordings(params), 'ismoderator': bigbluebuttonService.isModerator(params)]) } def join = { if( ltiService.consumerMap == null) ltiService.initConsumerMap() log.debug CONTROLLER_NAME + "#join" - Map<String, String> result - + def result def sessionParams = session["params"] - if( sessionParams != null ) { log.debug "params: " + params log.debug "sessionParams: " + sessionParams result = doJoinMeeting(sessionParams) } else { result = new HashMap<String, String>() - result.put("resultMessageKey", "InvalidSession") - result.put("resultMessage", "Invalid session. User can not execute this action.") + result.put("messageKey", "InvalidSession") + result.put("message", "Invalid session. User can not execute this action.") } - - if( result.containsKey("resultMessageKey")) { - log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']" - render(view: "error", model: ['resultMessageKey': result.get("resultMessageKey"), 'resultMessage': result.get("resultMessage")]) + if (result != null && result.containsKey("messageKey")) { + log.debug "Error [messageKey:'" + result.get("messageKey") + "', message:'" + result.get("message") + "']" + render(view: "error", model: ['messageKey': result.get("messageKey"), 'message': result.get("message")]) } } def publish = { log.debug CONTROLLER_NAME + "#publish" Map<String, String> result - def sessionParams = session["params"] - if( sessionParams == null ) { result = new HashMap<String, String>() - result.put("resultMessageKey", "InvalidSession") - result.put("resultMessage", "Invalid session. User can not execute this action.") + result.put("messageKey", "InvalidSession") + result.put("message", "Invalid session. User can not execute this action.") } else if ( !bigbluebuttonService.isModerator(sessionParams) ) { result = new HashMap<String, String>() - result.put("resultMessageKey", "NotAllowed") - result.put("resultMessage", "User not allowed to execute this action.") + result.put("messageKey", "NotAllowed") + result.put("message", "User not allowed to execute this action.") } else { - //Execute the publish command + // Execute the publish command result = bigbluebuttonService.doPublishRecordings(params) } - - if( result.containsKey("resultMessageKey")) { - log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']" - render(view: "error", model: ['resultMessageKey': result.get("resultMessageKey"), 'resultMessage': result.get("resultMessage")]) + if( result.containsKey("messageKey")) { + log.debug "Error [messageKey:'" + result.get("messageKey") + "', message:'" + result.get("message") + "']" + render(view: "error", model: ['messageKey': result.get("messageKey"), 'message': result.get("message")]) } else { render(view: "index", model: ['params': sessionParams, 'recordingList': getSanitizedRecordings(sessionParams), 'ismoderator': bigbluebuttonService.isModerator(sessionParams)]) } @@ -175,25 +153,22 @@ class ToolController { def delete = { log.debug CONTROLLER_NAME + "#delete" Map<String, String> result - def sessionParams = session["params"] - if( sessionParams == null ) { result = new HashMap<String, String>() - result.put("resultMessageKey", "InvalidSession") - result.put("resultMessage", "Invalid session. User can not execute this action.") + result.put("messageKey", "InvalidSession") + result.put("message", "Invalid session. User can not execute this action.") } else if ( !bigbluebuttonService.isModerator(sessionParams) ) { result = new HashMap<String, String>() - result.put("resultMessageKey", "NotAllowed") - result.put("resultMessage", "User not allowed to execute this action.") + result.put("messageKey", "NotAllowed") + result.put("message", "User not allowed to execute this action.") } else { - //Execute the delete command + // Execute the delete command. result = bigbluebuttonService.doDeleteRecordings(params) } - - if( result.containsKey("resultMessageKey")) { - log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']" - render(view: "error", model: ['resultMessageKey': result.get("resultMessageKey"), 'resultMessage': result.get("resultMessage")]) + if( result.containsKey("messageKey")) { + log.debug "Error [messageKey:'" + result.get("messageKey") + "', message:'" + result.get("message") + "']" + render(view: "error", model: ['messageKey': result.get("messageKey"), 'message': result.get("message")]) } else { render(view: "index", model: ['params': sessionParams, 'recordingList': getSanitizedRecordings(sessionParams), 'ismoderator': bigbluebuttonService.isModerator(sessionParams)]) } @@ -203,48 +178,39 @@ class ToolController { String locale = params.get(Parameter.LAUNCH_LOCALE) locale = (locale == null || locale.equals("")?"en":locale) String[] localeCodes = locale.split("_") - //Localize the default welcome message - if( localeCodes.length > 1 ) + // Localize the default welcome message + session['org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE'] = new Locale(localeCodes[0]) + if (localeCodes.length > 1) { session['org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE'] = new Locale(localeCodes[0], localeCodes[1]) - else - session['org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE'] = new Locale(localeCodes[0]) + } } private Object doJoinMeeting(Map<String, String> params) { - Map<String, String> result = new HashMap<String, String>() - setLocalization(params) String welcome = message(code: "bigbluebutton.welcome.header", args: ["\"{0}\"", "\"{1}\""]) + "<br>" - // Check for [custom_]welcome parameter being passed from the LTI - if ( params.containsKey(Parameter.CUSTOM_WELCOME) && params.get(Parameter.CUSTOM_WELCOME) != null ) { + if (params.containsKey(Parameter.CUSTOM_WELCOME) && params.get(Parameter.CUSTOM_WELCOME) != null) { welcome = params.get(Parameter.CUSTOM_WELCOME) + "<br>" log.debug "Overriding default welcome message with: [" + welcome + "]" } - - if ( params.containsKey(Parameter.CUSTOM_RECORD) && Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) || ltiService.allRecordedByDefault() ) { + if (params.containsKey(Parameter.CUSTOM_RECORD) && Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) || ltiService.allRecordedByDefault()) { welcome += "<br><b>" + message(code: "bigbluebutton.welcome.record") + "</b><br>" log.debug "Adding record warning to welcome message, welcome is now: [" + welcome + "]" } - - if ( params.containsKey(Parameter.CUSTOM_DURATION) && Integer.parseInt(params.get(Parameter.CUSTOM_DURATION)) > 0 ) { + if (params.containsKey(Parameter.CUSTOM_DURATION) && Integer.parseInt(params.get(Parameter.CUSTOM_DURATION)) > 0) { welcome += "<br><b>" + message(code: "bigbluebutton.welcome.duration", args: [params.get(Parameter.CUSTOM_DURATION)]) + "</b><br>" log.debug "Adding duration warning to welcome message, welcome is now: [" + welcome + "]" } - welcome += "<br>" + message(code: "bigbluebutton.welcome.footer") + "<br>" - String destinationURL = bigbluebuttonService.getJoinURL(params, welcome, ltiService.mode) - log.debug "redirecting to " + destinationURL - - if( destinationURL != null ) { - redirect(url:destinationURL) - } else { - result.put("resultMessageKey", "BigBlueButtonServerError") - result.put("resultMessage", "The join could not be completed") + if (destinationURL == null) { + Map<String, String> result = new HashMap<String, String>() + result.put("messageKey", "BigBlueButtonServerError") + result.put("message", "The join could not be completed") + return result } - - return result + log.debug "It is redirecting to " + destinationURL + redirect(url:destinationURL) } /** @@ -258,14 +224,15 @@ class ToolController { if (key == "action" || key == "controller" || key == "format") { // Ignore as these are the grails controller and action tied to this request. continue - } else if (key == "oauth_signature") { - // We don't need this as part of the base string + } + if (key == "oauth_signature") { + // We don't need this as part of the base string. continue - } else if (key == "request_method") { - // As this is was added by the controller, we don't want it as part of the base string + } + if (key == "request_method") { + // As this is was added by the controller, we don't want it as part of the base string. continue } - reqProp.setProperty(key, params.get(key)); } return reqProp @@ -279,24 +246,19 @@ class ToolController { */ private boolean hasAllRequiredParams(Map<String, String> params, ArrayList<String> missingParams) { log.debug "Checking for required parameters" - - boolean hasAllParams = true - if ( ltiService.hasRestrictedAccess() && !params.containsKey(Parameter.CONSUMER_ID) ) { + if (ltiService.hasRestrictedAccess() && !params.containsKey(Parameter.CONSUMER_ID)) { missingParams.add(Parameter.CONSUMER_ID); - hasAllParams = false; + return false } - - if ( ltiService.hasRestrictedAccess() && !params.containsKey(Parameter.OAUTH_SIGNATURE)) { + if (ltiService.hasRestrictedAccess() && !params.containsKey(Parameter.OAUTH_SIGNATURE)) { missingParams.add(Parameter.OAUTH_SIGNATURE); - hasAllParams = false; + return false } - - if ( !params.containsKey(Parameter.RESOURCE_LINK_ID) ) { + if (!params.containsKey(Parameter.RESOURCE_LINK_ID)) { missingParams.add(Parameter.RESOURCE_LINK_ID); - hasAllParams = false; + return false } - - return hasAllParams + return true } /** @@ -309,32 +271,23 @@ class ToolController { * @return - TRUE if the signatures matches the calculated signature */ private boolean checkValidSignature(String method, String url, String conSecret, Properties postProp, String signature) { - def validSignature = false - - if ( ltiService.hasRestrictedAccess() ) { - try { - OAuthMessage oam = new OAuthMessage(method, url, postProp.entrySet()) - //log.debug "OAuthMessage oam = " + oam.toString() - - HMAC_SHA1 hmac = new HMAC_SHA1() - //log.debug "HMAC_SHA1 hmac = " + hmac.toString() - - hmac.setConsumerSecret(conSecret) - - log.debug "Base Message String = [ " + hmac.getBaseString(oam) + " ]\n" - String calculatedSignature = hmac.getSignature(hmac.getBaseString(oam)) - log.debug "Calculated: " + calculatedSignature + " Received: " + signature - - validSignature = calculatedSignature.equals(signature) - } catch( Exception e ) { - log.debug "Exception error: " + e.message - } - - } else { - validSignature = true + if (!ltiService.hasRestrictedAccess()) { + return true; + } + try { + OAuthMessage oam = new OAuthMessage(method, url, postProp.entrySet()) + //log.debug "OAuthMessage oam = " + oam.toString() + HMAC_SHA1 hmac = new HMAC_SHA1() + //log.debug "HMAC_SHA1 hmac = " + hmac.toString() + hmac.setConsumerSecret(conSecret) + log.debug "Base Message String = [ " + hmac.getBaseString(oam) + " ]\n" + String calculatedSignature = hmac.getSignature(hmac.getBaseString(oam)) + log.debug "Calculated: " + calculatedSignature + " Received: " + signature + return calculatedSignature.equals(signature) + } catch( Exception e ) { + log.debug "Exception error: " + e.message + return false } - - return validSignature } /** @@ -343,20 +296,34 @@ class ToolController { * @return the key:val pairs needed for Basic LTI */ private List<Object> getSanitizedRecordings(Map<String, String> params) { - List<Object> recordings = bigbluebuttonService.getRecordings(params) - for(Map<String, Object> recording: recordings){ - /// Calculate duration + def recordings = new ArrayList<Object>() + def getRecordingsResponse = bigbluebuttonService.getRecordings(params) + if (getRecordingsResponse == null) { + return recordings + } + Object response = (Object)getRecordingsResponse.get("recording") + if (response instanceof Map<?,?>) { + recordings.add(response) + } + if (response instanceof Collection<?>) { + recordings = response + } + // Sanitize recordings + Iterator i = recordings.iterator(); + while (i.hasNext()) { + def recording = i.next() + // Calculate duration. long endTime = Long.parseLong((String)recording.get("endTime")) endTime -= (endTime % 1000) long startTime = Long.parseLong((String)recording.get("startTime")) startTime -= (startTime % 1000) int duration = (endTime - startTime) / 60000 - /// Add duration + // Add duration. recording.put("duration", duration ) - /// Calculate reportDate + // Calculate reportDate. DateFormat df = new SimpleDateFormat(message(code: "tool.view.dateFormat")) String reportDate = df.format(new Date(startTime)) - /// Add reportDate + // Add reportDate. recording.put("reportDate", reportDate) recording.put("unixDate", startTime / 1000) } @@ -399,4 +366,9 @@ class ToolController { return cartridge } + + private void renderError(key, message) { + log.debug "Error [resultMessageKey:'" + key + "', resultMessage:'" + message + "']" + render(view: "error", model: ['resultMessageKey': key, 'resultMessage': message]) + } } diff --git a/bbb-lti/grails-app/i18n/messages.properties b/bbb-lti/grails-app/i18n/messages.properties index a9a014740524315d68b6ab5d1e78b046d2f031de..0385dd5f83b75874d4fdde0d3eb08f7b26093b74 100644 --- a/bbb-lti/grails-app/i18n/messages.properties +++ b/bbb-lti/grails-app/i18n/messages.properties @@ -17,7 +17,7 @@ # # The welcome.header can be static, however if you want the name of the activity (meeting) to be injected use {0} as part of the text -# {1} can be used to inject the name of the course +# {1} can be used to inject the name of the course bigbluebutton.welcome.header=Welcome to <b>{0}</b>! bigbluebutton.welcome.footer=To understand how BigBlueButton works see our <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the headset icon (upper-left hand corner). <b>Please use a headset to avoid causing noise for others. bigbluebutton.welcome.record=This meeting is being recorded @@ -38,9 +38,10 @@ tool.view.recording.unpublish=Unpublish tool.view.recording.delete=Delete tool.view.activity=Activity tool.view.description=Description +tool.view.preview=Preview tool.view.date=Date tool.view.duration=Duration tool.view.actions=Actions tool.view.dateFormat=E, MMM dd, yyyy HH:mm:ss Z -tool.error.general=Connection could not be established. \ No newline at end of file +tool.error.general=Connection could not be established. diff --git a/bbb-lti/grails-app/i18n/messages_es.properties b/bbb-lti/grails-app/i18n/messages_es.properties index 7e0eedbf335ba1e69dffed503faf3e860a44f3e0..b749c09d525a5fb43763ccb414654d6d5723af04 100644 --- a/bbb-lti/grails-app/i18n/messages_es.properties +++ b/bbb-lti/grails-app/i18n/messages_es.properties @@ -39,9 +39,10 @@ tool.view.recording.confirmation.yes=Si tool.view.recording.confirmation.no=No tool.view.activity=Actividad tool.view.description=Descripción +tool.view.preview=Vista preliminar tool.view.date=Fecha tool.view.duration=Duración tool.view.actions=Acciones tool.view.dateFormat=E, MMM dd, yyyy HH:mm:ss Z -tool.error.general=No pudo estableserce la conexión. \ No newline at end of file +tool.error.general=No pudo estableserce la conexión. diff --git a/bbb-lti/grails-app/i18n/messages_fr.properties b/bbb-lti/grails-app/i18n/messages_fr.properties index 70c9fd7fbda9b803d3c1ecbda3db82b568235148..6d56c9e0d0cf9d7f7d37af1474c0815632f13ee0 100644 --- a/bbb-lti/grails-app/i18n/messages_fr.properties +++ b/bbb-lti/grails-app/i18n/messages_fr.properties @@ -1,42 +1,43 @@ -# -# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ -# -# Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). -# -# This program is free software; you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free Software -# Foundation; either version 3.0 of the License, or (at your option) any later -# version. -# -# BigBlueButton 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. -# - -bigbluebutton.welcome.header=<br>Bienvenue au <b>{0}</b>!<br> -bigbluebutton.welcome.footer=<br>Pour comprendre comment fonctionne BigBlueButton, consultez les <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>didacticiels vidéo</u></a>.<br><br>Pour activer l'audio cliquez sur l'icône du casque à écouteurs (coin supérieur gauche). <b>S'il vous pla�t utiliser le casque pour éviter de causer du bruit.</b> - -tool.view.app=BigBlueButton -tool.view.title=LTI Interface pour BigBlueButton -tool.view.join=Saisie de la réunion -tool.view.recording=Enregistrement -tool.view.recording.format.presentation=presentation -tool.view.recording.format.video=video -tool.view.recording.delete.confirmation=Veillez à supprimer définitivement cet enregistrement? -tool.view.recording.delete.confirmation.warning=Attention -tool.view.recording.delete.confirmation.yes=Oui -tool.view.recording.delete.confirmation.no=Non -tool.view.recording.publish=Publier -tool.view.recording.unpublish=Dépublier -tool.view.recording.delete=Supprimer -tool.view.activity=Activité -tool.view.description=Description -tool.view.date=Date -tool.view.duration=Durée -tool.view.actions=Actions -tool.view.dateFormat=E, MMM dd, yyyy HH:mm:ss Z - -tool.error.general=Pas possible établir la connection. \ No newline at end of file +# +# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ +# +# Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). +# +# This program is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free Software +# Foundation; either version 3.0 of the License, or (at your option) any later +# version. +# +# BigBlueButton 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. +# + +bigbluebutton.welcome.header=<br>Bienvenue au <b>{0}</b>!<br> +bigbluebutton.welcome.footer=<br>Pour comprendre comment fonctionne BigBlueButton, consultez les <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>didacticiels vidéo</u></a>.<br><br>Pour activer l'audio cliquez sur l'icône du casque à écouteurs (coin supérieur gauche). <b>S'il vous pla�t utiliser le casque pour éviter de causer du bruit.</b> + +tool.view.app=BigBlueButton +tool.view.title=LTI Interface pour BigBlueButton +tool.view.join=Saisie de la réunion +tool.view.recording=Enregistrement +tool.view.recording.format.presentation=presentation +tool.view.recording.format.video=video +tool.view.recording.delete.confirmation=Veillez à supprimer définitivement cet enregistrement? +tool.view.recording.delete.confirmation.warning=Attention +tool.view.recording.delete.confirmation.yes=Oui +tool.view.recording.delete.confirmation.no=Non +tool.view.recording.publish=Publier +tool.view.recording.unpublish=Dépublier +tool.view.recording.delete=Supprimer +tool.view.activity=Activité +tool.view.description=Description +tool.view.preview=Apreçu +tool.view.date=Date +tool.view.duration=Durée +tool.view.actions=Actions +tool.view.dateFormat=E, MMM dd, yyyy HH:mm:ss Z + +tool.error.general=Pas possible établir la connection. diff --git a/bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy b/bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy index 297a6ce55422c33579812f4de11ead16c2791793..17714737b5b13d625525ff043bdcfbd90ac3f930 100644 --- a/bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy +++ b/bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy @@ -34,6 +34,10 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.XML; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -63,20 +67,20 @@ class BigbluebuttonService { try { docBuilder = docBuilderFactory.newDocumentBuilder() } catch (ParserConfigurationException e) { - logger.error("Failed to initialise BaseProxy", e) + log.error("Failed to initialise BaseProxy", e) } - //Instantiate bbbProxy and initialize it with default url and salt bbbProxy = new Proxy(url, salt) } public String getJoinURL(params, welcome, mode){ - //Set the injected values - if( !url.equals(bbbProxy.url) && !url.equals("") ) bbbProxy.setUrl(url) - if( !salt.equals(bbbProxy.salt) && !salt.equals("") ) bbbProxy.setSalt(salt) - - String joinURL = null - + // Set the injected values + if (!url.equals(bbbProxy.url) && !url.equals("")) { + bbbProxy.setUrl(url) + } + if (!salt.equals(bbbProxy.salt) && !salt.equals("")) { + bbbProxy.setSalt(salt) + } String meetingName = getValidatedMeetingName(params.get(Parameter.RESOURCE_LINK_TITLE)) String meetingID = getValidatedMeetingId(params.get(Parameter.RESOURCE_LINK_ID), params.get(Parameter.CONSUMER_ID)) String attendeePW = DigestUtils.shaHex("ap" + params.get(Parameter.RESOURCE_LINK_ID) + params.get(Parameter.CONSUMER_ID)) @@ -86,7 +90,6 @@ class BigbluebuttonService { String userFullName = getValidatedUserFullName(params, isModerator) String courseTitle = getValidatedCourseTitle(params.get(Parameter.COURSE_TITLE)) String userID = getValidatedUserId(params.get(Parameter.USER_ID)) - Integer voiceBridge = 0 String record = false Integer duration = 0 @@ -95,98 +98,93 @@ class BigbluebuttonService { record = getValidatedBBBRecord(params.get(Parameter.CUSTOM_RECORD)) || ltiService.allRecordedByDefault() duration = getValidatedBBBDuration(params.get(Parameter.CUSTOM_DURATION)) } - Boolean allModerators = Boolean.valueOf(false) if ( params.containsKey(Parameter.CUSTOM_ALL_MODERATORS) ) { allModerators = Boolean.parseBoolean(params.get(Parameter.CUSTOM_ALL_MODERATORS)) } - String[] values = [meetingName, courseTitle] String welcomeMsg = MessageFormat.format(welcome, values) - String meta = getMonitoringMetaData(params) - - String createURL = getCreateURL( meetingName, meetingID, attendeePW, moderatorPW, welcomeMsg, voiceBridge, logoutURL, record, duration, meta ) - log.debug "createURL: " + createURL - Map<String, Object> createResponse = doAPICall(createURL) - log.debug "createResponse: " + createResponse - - if( createResponse != null){ - String returnCode = (String) createResponse.get("returncode") - String messageKey = (String) createResponse.get("messageKey") - if ( Proxy.APIRESPONSE_SUCCESS.equals(returnCode) || - (Proxy.APIRESPONSE_FAILED.equals(returnCode) && (Proxy.MESSAGEKEY_IDNOTUNIQUE.equals(messageKey) || Proxy.MESSAGEKEY_DUPLICATEWARNING.equals(messageKey)) ) ){ - joinURL = bbbProxy.getJoinURL( userFullName, meetingID, (isModerator || allModerators)? moderatorPW: attendeePW, (String) createResponse.get("createTime"), userID); - } + String createURL = getCreateURL(meetingName, meetingID, attendeePW, moderatorPW, welcomeMsg, voiceBridge, logoutURL, record, duration, meta) + Map<String, Object> responseAPICall = doAPICall(createURL) + log.info "responseAPICall: " + responseAPICall + if (responseAPICall == null) { + return null } - + Object response = (Object)responseAPICall.get("response") + String returnCode = (String)response.get("returncode") + String messageKey = (String)response.get("messageKey") + if (!Proxy.APIRESPONSE_SUCCESS.equals(returnCode) || + !Proxy.MESSAGEKEY_IDNOTUNIQUE.equals(messageKey) && + !Proxy.MESSAGEKEY_DUPLICATEWARNING.equals(messageKey) && + !"".equals(messageKey)) { + return null + } + def joinURL = bbbProxy.getJoinURL(userFullName, meetingID, (isModerator || allModerators)? moderatorPW: attendeePW, (String) response.get("createTime"), userID) + log.info "joinURL: " + joinURL return joinURL } - public Object getRecordings(params){ - //Set the injected values - if( !url.equals(bbbProxy.url) && !url.equals("") ) bbbProxy.setUrl(url) - if( !salt.equals(bbbProxy.salt) && !salt.equals("") ) bbbProxy.setSalt(salt) - + public Object getRecordings(params) { + // Set the injected values + if (!url.equals(bbbProxy.url) && !url.equals("")) { + bbbProxy.setUrl(url) + } + if (!salt.equals(bbbProxy.salt) && !salt.equals("")) { + bbbProxy.setSalt(salt) + } String meetingID = getValidatedMeetingId(params.get(Parameter.RESOURCE_LINK_ID), params.get(Parameter.CONSUMER_ID)) - - String recordingsURL = bbbProxy.getGetRecordingsURL( meetingID ) - log.debug "recordingsURL: " + recordingsURL - Map<String, Object> recordings = doAPICall(recordingsURL) - - if( recordings != null){ - String returnCode = (String) recordings.get("returncode") - String messageKey = (String) recordings.get("messageKey") - if ( Proxy.APIRESPONSE_SUCCESS.equals(returnCode) && messageKey == null ){ - return recordings.get("recordings") - } + String recordingsURL = bbbProxy.getGetRecordingsURL(meetingID) + Map<String, Object> responseAPICall = doAPICall(recordingsURL) + if (responseAPICall == null) { + return null } - - return null + Object response = (Object)responseAPICall.get("response") + String returnCode = (String)response.get("returncode") + String messageKey = (String)response.get("messageKey") + if (!Proxy.APIRESPONSE_SUCCESS.equals(returnCode) || messageKey != null) { + return null + } + Object recordings = (Object)response.get("recordings") + return recordings } public Object doDeleteRecordings(params){ - //Set the injected values - if( !url.equals(bbbProxy.url) && !url.equals("") ) bbbProxy.setUrl(url) - if( !salt.equals(bbbProxy.salt) && !salt.equals("") ) bbbProxy.setSalt(salt) - - Map<String, Object> result - + // Set the injected values + if (!url.equals(bbbProxy.url) && !url.equals("")) { + bbbProxy.setUrl(url) + } + if (!salt.equals(bbbProxy.salt) && !salt.equals("")) { + bbbProxy.setSalt(salt) + } String recordingId = getValidatedBBBRecordingId(params.get(Parameter.BBB_RECORDING_ID)) - - if( !recordingId.equals("") ){ + if (!recordingId.equals("")) { String deleteRecordingsURL = bbbProxy.getDeleteRecordingsURL( recordingId ) - log.debug "deleteRecordingsURL: " + deleteRecordingsURL - result = doAPICall(deleteRecordingsURL) - } else { - result = new HashMap<String, String>() - result.put("resultMessageKey", "InvalidRecordingId") - result.put("resultMessage", "RecordingId is invalid. The recording can not be deleted.") + return doAPICall(deleteRecordingsURL) } - + def result = new HashMap<String, String>() + result.put("messageKey", "InvalidRecordingId") + result.put("message", "RecordingId is invalid. The recording can not be deleted.") return result } public Object doPublishRecordings(params){ - //Set the injected values - if( !url.equals(bbbProxy.url) && !url.equals("") ) bbbProxy.setUrl(url) - if( !salt.equals(bbbProxy.salt) && !salt.equals("") ) bbbProxy.setSalt(salt) - - Map<String, Object> result - + // Set the injected values + if (!url.equals(bbbProxy.url) && !url.equals("")) { + bbbProxy.setUrl(url) + } + if (!salt.equals(bbbProxy.salt) && !salt.equals("")) { + bbbProxy.setSalt(salt) + } String recordingId = getValidatedBBBRecordingId(params.get(Parameter.BBB_RECORDING_ID)) String publish = getValidatedBBBRecordingPublished(params.get(Parameter.BBB_RECORDING_PUBLISHED)) - if( !recordingId.equals("") ){ String publishRecordingsURL = bbbProxy.getPublishRecordingsURL( recordingId, "true".equals(publish)?"false":"true" ) - log.debug "publishRecordingsURL: " + publishRecordingsURL - result = doAPICall(publishRecordingsURL) - } else { - result = new HashMap<String, String>() - result.put("resultMessageKey", "InvalidRecordingId") - result.put("resultMessage", "RecordingId is invalid. The recording can not be deleted.") + return doAPICall(publishRecordingsURL) } - + def result = new HashMap<String, String>() + result.put("messageKey", "InvalidRecordingId") + result.put("message", "RecordingId is invalid. The recording can not be deleted.") return result } @@ -219,14 +217,14 @@ class BigbluebuttonService { String userFirstName = params.get(Parameter.USER_FIRSTNAME) String userLastName = params.get(Parameter.USER_LASTNAME) if( userFullName == null || userFullName == "" ){ - if( userFirstName != null && userFirstName != "" ){ + if (userFirstName != null && userFirstName != "") { userFullName = userFirstName } - if( userLastName != null && userLastName != "" ){ + if (userLastName != null && userLastName != "") { userFullName += userFullName.length() > 0? " ": "" userFullName += userLastName } - if( userFullName == null || userFullName == "" ){ + if (userFullName == null || userFullName == "") { userFullName = isModerator? "Moderator" : "Attendee" } } @@ -263,8 +261,7 @@ class BigbluebuttonService { private String getMonitoringMetaData(params){ String meta - - meta = "meta_origin=" + bbbProxy.getStringEncoded(params.get(Parameter.TOOL_CONSUMER_CODE) == null? "": params.get(Parameter.TOOL_CONSUMER_CODE)) + meta = "meta_origin=" + bbbProxy.getStringEncoded(params.get(Parameter.TOOL_CONSUMER_CODE) == null? "": params.get(Parameter.TOOL_CONSUMER_CODE)) meta += "&meta_originVersion=" + bbbProxy.getStringEncoded(params.get(Parameter.TOOL_CONSUMER_VERSION) == null? "": params.get(Parameter.TOOL_CONSUMER_VERSION)) meta += "&meta_originServerCommonName=" + bbbProxy.getStringEncoded(params.get(Parameter.TOOL_CONSUMER_INSTANCE_DESCRIPTION) == null? "": params.get(Parameter.TOOL_CONSUMER_INSTANCE_DESCRIPTION)) meta += "&meta_originServerUrl=" + bbbProxy.getStringEncoded(params.get(Parameter.TOOL_CONSUMER_INSTANCE_URL) == null? "": params.get(Parameter.TOOL_CONSUMER_INSTANCE_URL)) @@ -272,25 +269,21 @@ class BigbluebuttonService { meta += "&meta_contextId=" + bbbProxy.getStringEncoded(params.get(Parameter.COURSE_ID) == null? "": params.get(Parameter.COURSE_ID)) meta += "&meta_contextActivity=" + bbbProxy.getStringEncoded(params.get(Parameter.RESOURCE_LINK_TITLE) == null? "": params.get(Parameter.RESOURCE_LINK_TITLE)) meta += "&meta_contextActivityDescription=" + bbbProxy.getStringEncoded(params.get(Parameter.RESOURCE_LINK_DESCRIPTION) == null? "": params.get(Parameter.RESOURCE_LINK_DESCRIPTION)) - return meta } /** Make an API call */ private Map<String, Object> doAPICall(String query) { StringBuilder urlStr = new StringBuilder(query); - try { // open connection - //log.debug("doAPICall.call: " + query ); - + log.debug("doAPICall.call: " + query ); URL url = new URL(urlStr.toString()); HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection(); httpConnection.setUseCaches(false); httpConnection.setDoOutput(true); httpConnection.setRequestMethod("GET"); httpConnection.connect(); - int responseCode = httpConnection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { // read response @@ -302,35 +295,27 @@ class BigbluebuttonService { reader = new BufferedReader(isr); String line = reader.readLine(); while (line != null) { - if( !line.startsWith("<?xml version=\"1.0\"?>")) + if( !line.startsWith("<?xml version=\"1.0\"?>")) { xml.append(line.trim()); + } line = reader.readLine(); } } finally { - if (reader != null) + if (reader != null) { reader.close(); - if (isr != null) + } + if (isr != null) { isr.close(); + } } httpConnection.disconnect(); - - // parse response + // Parse response. //log.debug("doAPICall.responseXml: " + xml); //Patch to fix the NaN error String stringXml = xml.toString(); stringXml = stringXml.replaceAll(">.\\s+?<", "><"); - - Document dom = null; - dom = docBuilder.parse(new InputSource( new StringReader(stringXml))); - - Map<String, Object> response = getNodesAsMap(dom, "response"); - //log.debug("doAPICall.responseMap: " + response); - - String returnCode = (String) response.get("returncode"); - if (Proxy.APIRESPONSE_FAILED.equals(returnCode)) { - log.debug("doAPICall." + (String) response.get("messageKey") + ": Message=" + (String) response.get("message")); - } - + JSONObject rootJSON = XML.toJSONObject(stringXml); + Map<String, Object> response = jsonToMap(rootJSON); return response; } else { log.debug("doAPICall.HTTPERROR: Message=" + "BBB server responded with HTTP status code " + responseCode); @@ -346,43 +331,43 @@ class BigbluebuttonService { } } - /** Get all nodes under the specified element tag name as a Java map */ - protected Map<String, Object> getNodesAsMap(Document dom, String elementTagName) { - Node firstNode = dom.getElementsByTagName(elementTagName).item(0); - return processNode(firstNode); + protected Map<String, Object> jsonToMap(JSONObject json) throws JSONException { + Map<String, Object> retMap = new HashMap<String, Object>(); + if(json != JSONObject.NULL) { + retMap = toMap(json); + } + return retMap; } - protected Map<String, Object> processNode(Node _node) { + protected Map<String, Object> toMap(JSONObject object) throws JSONException { Map<String, Object> map = new HashMap<String, Object>(); - NodeList responseNodes = _node.getChildNodes(); - for (int i = 0; i < responseNodes.getLength(); i++) { - Node node = responseNodes.item(i); - String nodeName = node.getNodeName().trim(); - if (node.getChildNodes().getLength() == 1 - && ( node.getChildNodes().item(0).getNodeType() == org.w3c.dom.Node.TEXT_NODE || node.getChildNodes().item(0).getNodeType() == org.w3c.dom.Node.CDATA_SECTION_NODE) ) { - String nodeValue = node.getTextContent(); - map.put(nodeName, nodeValue != null ? nodeValue.trim() : null); - - } else if (node.getChildNodes().getLength() == 0 - && node.getNodeType() != org.w3c.dom.Node.TEXT_NODE - && node.getNodeType() != org.w3c.dom.Node.CDATA_SECTION_NODE) { - map.put(nodeName, ""); - - } else if ( node.getChildNodes().getLength() >= 1 - && node.getChildNodes().item(0).getChildNodes().item(0).getNodeType() != org.w3c.dom.Node.TEXT_NODE - && node.getChildNodes().item(0).getChildNodes().item(0).getNodeType() != org.w3c.dom.Node.CDATA_SECTION_NODE ) { - - List<Object> list = new ArrayList<Object>(); - for (int c = 0; c < node.getChildNodes().getLength(); c++) { - Node n = node.getChildNodes().item(c); - list.add(processNode(n)); - } - map.put(nodeName, list); - - } else { - map.put(nodeName, processNode(node)); + Iterator<String> keysItr = object.keys(); + while(keysItr.hasNext()) { + String key = keysItr.next(); + Object value = object.get(key); + if(value instanceof JSONArray) { + value = toList((JSONArray) value); + } + else if(value instanceof JSONObject) { + value = toMap((JSONObject) value); } + map.put(key, value); } return map; } + + protected List<Object> toList(JSONArray array) throws JSONException { + List<Object> list = new ArrayList<Object>(); + for(int i = 0; i < array.length(); i++) { + Object value = array.get(i); + if(value instanceof JSONArray) { + value = toList((JSONArray) value); + } + else if(value instanceof JSONObject) { + value = toMap((JSONObject) value); + } + list.add(value); + } + return list; + } } diff --git a/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy b/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy index c78c127ba0eecd608ccd347761b02c4cb495a86b..2465c8e330e1c949e5d50538ae8eb8f0aaa38fd9 100644 --- a/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy +++ b/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy @@ -46,42 +46,37 @@ class LtiService { private Map<String, String> getConsumer(consumerId) { Map<String, String> consumer = null - - if( this.consumerMap.containsKey(consumerId) ){ + if (this.consumerMap.containsKey(consumerId)) { consumer = new HashMap<String, String>() consumer.put("key", consumerId); consumer.put("secret", this.consumerMap.get(consumerId)) } - return consumer } - private void initConsumerMap(){ + private void initConsumerMap() { this.consumerMap = new HashMap<String, String>() String[] consumers = this.consumers.split(",") - //for( int i=0; i < consumers.length; i++){ - if ( consumers.length > 0 ){ + if ( consumers.length > 0 ) { int i = 0; String[] consumer = consumers[i].split(":") if( consumer.length == 2 ){ this.consumerMap.put(consumer[0], consumer[1]) } } - } - public String sign(String sharedSecret, String data) throws Exception - { + public String sign(String sharedSecret, String data) + throws Exception { Mac mac = setKey(sharedSecret) - // Signed String must be BASE64 encoded. byte[] signBytes = mac.doFinal(data.getBytes("UTF8")); String signature = encodeBase64(signBytes); return signature; } - private Mac setKey(String sharedSecret) throws Exception - { + private Mac setKey(String sharedSecret) + throws Exception { Mac mac = Mac.getInstance("HmacSHA1"); byte[] keyBytes = sharedSecret.getBytes("UTF8"); SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1"); @@ -110,7 +105,6 @@ class LtiService { def boolean isSSLEnabled(String query) { def ssl_enabled = false - log.debug("Pinging SSL connection") try { // open connection @@ -122,14 +116,12 @@ class LtiService { httpConnection.setRequestMethod("HEAD") httpConnection.setConnectTimeout(5000) httpConnection.connect() - int responseCode = httpConnection.getResponseCode() if (responseCode == HttpURLConnection.HTTP_OK) { ssl_enabled = true } else { log.debug("HTTPERROR: Message=" + "BBB server responded with HTTP status code " + responseCode) } - } catch(IOException e) { log.debug("IOException: Message=" + e.getMessage()) } catch(IllegalArgumentException e) { @@ -148,4 +140,8 @@ class LtiService { def boolean allRecordedByDefault() { return Boolean.parseBoolean(this.recordedByDefault); } + + def String getScheme(request) { + return request.isSecure() ? "https" : "http" + } } diff --git a/bbb-lti/grails-app/views/tool/error.gsp b/bbb-lti/grails-app/views/tool/error.gsp index e9cc755530f13de18d75ac2315554e6b4db7f9e6..e1442fa84d478f588c0a4b96420ae898ef3d0d3c 100644 --- a/bbb-lti/grails-app/views/tool/error.gsp +++ b/bbb-lti/grails-app/views/tool/error.gsp @@ -1,35 +1,35 @@ -<html> - <head> - <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/> - <title>Error</title> - <asset:stylesheet src="bootstrap.css"/> - <asset:stylesheet src="tool.css"/> - <asset:javascript src="jquery.js"/> - <asset:javascript src="bootstrap.js"/> - </head> - <body> - <div class="body"> - <br/><br/> - <div class="container"> - <g:if test="${ (resultMessageKey == 'InvalidEPortfolioUserId')}"> - <div class="alert alert-warning"> - ${resultMessage} - </div> - </g:if> - <g:else> - <div class="alert alert-danger"> - <g:message code="tool.error.general" /> - </div> - </g:else> - </div> - </div> - <!-- { - "error": { - "messageKey": "${resultMessageKey}", - "message": "${resultMessage}" - } - } - --> - <br/><br/> - </body> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/> + <title>Error</title> + <asset:stylesheet src="bootstrap.css"/> + <asset:stylesheet src="tool.css"/> + <asset:javascript src="jquery.js"/> + <asset:javascript src="bootstrap.js"/> + </head> + <body> + <div class="body"> + <br/><br/> + <div class="container"> + <g:if test="${ (resultMessageKey == 'InvalidEPortfolioUserId')}"> + <div class="alert alert-warning"> + ${resultMessage} + </div> + </g:if> + <g:else> + <div class="alert alert-danger"> + <g:message code="tool.error.general" /> + </div> + </g:else> + </div> + </div> + <!-- { + "error": { + "messageKey": "${resultMessageKey}", + "message": "${resultMessage}" + } + } + --> + <br/><br/> + </body> </html> \ No newline at end of file diff --git a/bbb-lti/grails-app/views/tool/index.gsp b/bbb-lti/grails-app/views/tool/index.gsp index 342e5a52848c26a9013be97c83b51501be5ba544..c6b8526b94f0cac1beaa7237b3364ad39067c339 100644 --- a/bbb-lti/grails-app/views/tool/index.gsp +++ b/bbb-lti/grails-app/views/tool/index.gsp @@ -1,77 +1,79 @@ -<html> - <head> - <title><g:message code="tool.view.title" /></title> - <link rel="shortcut icon" href="${assetPath(src: 'favicon.ico')}" type="image/x-icon"> - <asset:stylesheet src="bootstrap.css"/> - <asset:stylesheet src="dataTables.bootstrap.min.css"/> - <asset:javascript src="jquery.js"/> - <asset:javascript src="jquery.dataTables.min.js"/> - <asset:javascript src="dataTables.bootstrap.min.js"/> - <asset:javascript src="dataTables.plugin.datetime.js"/> - <asset:javascript src="moment-with-locales.min.js"/> - <asset:javascript src="bootstrap.js"/> - <asset:javascript src="bootstrap-confirmation.min.js"/> - <asset:javascript src="tool.js"/> - </head> - <body> - <h1 style="margin-left:20px; text-align: center;"><a title="<g:message code="tool.view.join" />" class="btn btn-primary btn-large" href="${createLink(controller:'tool', action:'join', id: '0')}"><g:message code="tool.view.join" /></a></h1> - <br><br> - <div class="container"> - <table id="recordings" class="table table-striped table-bordered dt-responsive" width="100%"> - <thead> - <tr> - <th class="header c0" style="text-align:center;" scope="col"><g:message code="tool.view.recording" /></th> - <th class="header c1" style="text-align:center;" scope="col"><g:message code="tool.view.activity" /></th> - <th class="header c2" style="text-align:center;" scope="col"><g:message code="tool.view.description" /></th> - <th class="header c3" style="text-align:center;" scope="col"><g:message code="tool.view.date" /></th> - <th class="header c4" style="text-align:center;" scope="col"><g:message code="tool.view.duration" /></th> - <g:if test="${ismoderator}"> - <th class="header c5 lastcol" style="text-align:center;" scope="col"><g:message code="tool.view.actions" /></th> - </g:if> - </tr> - </thead> - <tbody> - <g:each in="${recordingList}" var="r"> - <g:if test="${ismoderator || r.published == 'true'}"> - <tr class="r0 lastrow"> - <td class="cell c0" style="text-align:center;"> - <g:if test="${r.published == 'true'}"> - <g:each in="${r.playback}" var="p"> - <a title="<g:message code="tool.view.recording.format.${p.type}" />" target="_new" href="${p.url}"><g:message code="tool.view.recording.format.${p.type}" /></a>  - </g:each> - </g:if> - </td> - <td class="cell c1" style="text-align:center;">${r.name}</td> - <td class="cell c2" style="text-align:center;">${r.metadata.contextactivitydescription}</td> - <td class="cell c3" style="text-align:center;">${r.unixDate}</td> - <td class="cell c4" style="text-align:center;">${r.duration}</td> - <g:if test="${ismoderator}"> - <td class="cell c5 lastcol" style="text-align:center;"> - <g:if test="${r.published == 'true'}"> - <a title="<g:message code="tool.view.recording.unpublish" />" class="btn btn-default btn-sm glyphicon glyphicon-eye-close" name="unpublish_recording" type="submit" value="${r.recordID}" href="${createLink(controller:'tool',action:'publish',id: '0')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}"></a> - </g:if> - <g:else> - <a title="<g:message code="tool.view.recording.publish" />" class="btn btn-default btn-sm glyphicon glyphicon-eye-open" name="publish_recording" type="submit" value="${r.recordID}" href="${createLink(controller:'tool',action:'publish',id: '0')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}"></a> - </g:else> - <a title="<g:message code="tool.view.recording.delete" />" class="btn btn-danger btn-sm glyphicon glyphicon-trash" name="delete_recording" value="${r.recordID}" - data-toggle="confirmation" - data-title="<g:message code="tool.view.recording.delete.confirmation.warning" />" - data-content="<g:message code="tool.view.recording.delete.confirmation" />" - data-btn-ok-label="<g:message code="tool.view.recording.delete.confirmation.yes" />" - data-btn-cancel-label="<g:message code="tool.view.recording.delete.confirmation.no" />" - data-placement="left" - href="${createLink(controller:'tool',action:'delete',id: '0')}?bbb_recording_id=${r.recordID}"> - </a> - </td> - </g:if> - </tr> - </g:if> - </g:each> - </tbody> - </table> - </div> - </body> - <g:javascript> - var locale = '${params.launch_presentation_locale}'; - </g:javascript> -</html> \ No newline at end of file +<html> + <head> + <title><g:message code="tool.view.title" /></title> + <link rel="shortcut icon" href="${assetPath(src: 'favicon.ico')}" type="image/x-icon"> + <asset:stylesheet src="bootstrap.css"/> + <asset:stylesheet src="dataTables.bootstrap.min.css"/> + <asset:javascript src="jquery.js"/> + <asset:javascript src="jquery.dataTables.min.js"/> + <asset:javascript src="dataTables.bootstrap.min.js"/> + <asset:javascript src="dataTables.plugin.datetime.js"/> + <asset:javascript src="moment-with-locales.min.js"/> + <asset:javascript src="bootstrap.js"/> + <asset:javascript src="bootstrap-confirmation.min.js"/> + <asset:javascript src="tool.js"/> + </head> + <body> + <h1 style="margin-left:20px; text-align: center;"><a title="<g:message code="tool.view.join" />" class="btn btn-primary btn-large" href="${createLink(controller:'tool', action:'join', id: '0')}"><g:message code="tool.view.join" /></a></h1> + <br><br> + <div class="container"> + <table id="recordings" class="table table-striped table-bordered dt-responsive" width="100%"> + <thead> + <tr> + <th class="header c0" style="text-align:center;" scope="col"><g:message code="tool.view.recording" /></th> + <th class="header c1" style="text-align:center;" scope="col"><g:message code="tool.view.activity" /></th> + <th class="header c2" style="text-align:center;" scope="col"><g:message code="tool.view.description" /></th> + <th class="header c3" style="text-align:center;" scope="col"><g:message code="tool.view.preview" /></th> + <th class="header c4" style="text-align:center;" scope="col"><g:message code="tool.view.date" /></th> + <th class="header c5" style="text-align:center;" scope="col"><g:message code="tool.view.duration" /></th> + <g:if test="${ismoderator}"> + <th class="header c6 lastcol" style="text-align:center;" scope="col"><g:message code="tool.view.actions" /></th> + </g:if> + </tr> + </thead> + <tbody> + <g:each in="${recordingList}" var="r"> + <g:if test="${ismoderator || r.published == 'true'}"> + <tr class="r0 lastrow"> + <td class="cell c0" style="text-align:center;"> + <g:if test="${r.published}"> + <g:each in="${r.playback}" var="format"> + <a title="<g:message code="tool.view.recording.format.${format.getValue().type}" />" target="_new" href="${format.getValue().url}"><g:message code="tool.view.recording.format.${format.getValue().type}" /></a>  + </g:each> + </g:if> + </td> + <td class="cell c1" style="text-align:left;">${r.name}</td> + <td class="cell c2" style="text-align:left;">${r.metadata.contextactivitydescription}</td> + <td class="cell c3" style="text-align:left;">${r.metadata.contextactivitydescription}</td> + <td class="cell c4" style="text-align:left;">${r.reportDate}</td> + <td class="cell c5" style="text-align:right;">${r.duration}</td> + <g:if test="${ismoderator}"> + <td class="cell c6 lastcol" style="text-align:center;"> + <g:if test="${r.published == 'true'}"> + <a title="<g:message code="tool.view.recording.unpublish" />" class="btn btn-default btn-sm glyphicon glyphicon-eye-close" name="unpublish_recording" type="submit" value="${r.recordID}" href="${createLink(controller:'tool',action:'publish',id: '0')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}"></a> + </g:if> + <g:else> + <a title="<g:message code="tool.view.recording.publish" />" class="btn btn-default btn-sm glyphicon glyphicon-eye-open" name="publish_recording" type="submit" value="${r.recordID}" href="${createLink(controller:'tool',action:'publish',id: '0')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}"></a> + </g:else> + <a title="<g:message code="tool.view.recording.delete" />" class="btn btn-danger btn-sm glyphicon glyphicon-trash" name="delete_recording" value="${r.recordID}" + data-toggle="confirmation" + data-title="<g:message code="tool.view.recording.delete.confirmation.warning" />" + data-content="<g:message code="tool.view.recording.delete.confirmation" />" + data-btn-ok-label="<g:message code="tool.view.recording.delete.confirmation.yes" />" + data-btn-cancel-label="<g:message code="tool.view.recording.delete.confirmation.no" />" + data-placement="left" + href="${createLink(controller:'tool',action:'delete',id: '0')}?bbb_recording_id=${r.recordID}"> + </a> + </td> + </g:if> + </tr> + </g:if> + </g:each> + </tbody> + </table> + </div> + </body> + <g:javascript> + var locale = '${params.launch_presentation_locale}'; + </g:javascript> +</html>