diff --git a/bbb-lti/.classpath b/bbb-lti/.classpath deleted file mode 100644 index 450fe04df4..0000000000 --- a/bbb-lti/.classpath +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/bbb-lti/application.properties b/bbb-lti/application.properties index e5656fae00..6a7eeaaa1d 100644 --- a/bbb-lti/application.properties +++ b/bbb-lti/application.properties @@ -1,5 +1,5 @@ #Grails Metadata file -#Thu Mar 20 10:48:08 PDT 2014 +#Wed Aug 27 13:06:23 PDT 2014 app.grails.version=2.3.6 app.name=lti -app.version=0.1.2 +app.version=0.2 diff --git a/bbb-lti/grails-app/conf/ApplicationResources.groovy b/bbb-lti/grails-app/conf/ApplicationResources.groovy index 6fe55b24a7..c0f50d4b63 100644 --- a/bbb-lti/grails-app/conf/ApplicationResources.groovy +++ b/bbb-lti/grails-app/conf/ApplicationResources.groovy @@ -15,7 +15,6 @@ You should have received a copy of the GNU Lesser General Public License along with BigBlueButton; if not, see . */ - modules = { application { resource url:'js/application.js' diff --git a/bbb-lti/grails-app/conf/BuildConfig.groovy b/bbb-lti/grails-app/conf/BuildConfig.groovy index cff8ee4071..83d7df24d9 100644 --- a/bbb-lti/grails-app/conf/BuildConfig.groovy +++ b/bbb-lti/grails-app/conf/BuildConfig.groovy @@ -15,8 +15,7 @@ You should have received a copy of the GNU Lesser General Public License along with BigBlueButton; if not, see . */ - -grails.servlet.version = "3.0" +grails.servlet.version = "3.0" // Change depending on target container compliance (2.5 or 3.0) grails.project.class.dir = "target/classes" grails.project.test.class.dir = "target/test-classes" grails.project.test.reports.dir = "target/test-reports" @@ -26,48 +25,76 @@ grails.project.source.level = 1.6 //grails.project.war.file = "target/${appName}-${appVersion}.war" grails.project.fork = [ + // configure settings for compilation JVM, note that if you alter the Groovy version forked compilation is required + // compile: [maxMemory: 256, minMemory: 64, debug: false, maxPerm: 256, daemon:true], + + // configure settings for the test-app JVM, uses the daemon by default test: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, daemon:true], + // configure settings for the run-app JVM run: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false], + // configure settings for the run-war JVM war: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false], + // configure settings for the Console UI JVM console: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256] ] grails.project.dependency.resolver = "maven" // or ivy grails.project.dependency.resolution = { + // inherit Grails' default dependencies inherits("global") { + // specify dependency exclusions here; for example, uncomment this to disable ehcache: + // excludes 'ehcache' } - log "error" - checksums true - legacyResolve false + log "error" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose' + checksums true // Whether to verify checksums on resolve + legacyResolve false // whether to do a secondary resolve on plugin installation, not advised and here for backwards compatibility repositories { - inherits true + inherits true // Whether to inherit repository definitions from plugins grailsPlugins() grailsHome() mavenLocal() grailsCentral() mavenCentral() - - mavenRepo "http://snapshots.repository.codehaus.org" - mavenRepo "http://repository.codehaus.org" - mavenRepo "http://download.java.net/maven/2/" - mavenRepo "http://repository.jboss.com/maven2/" - //mavenRepo "https://raw.github.com/blindsidenetworks/oauth/mvn-repo/" + // uncomment these (or add new ones) to enable remote dependency resolution from public Maven repositories + //mavenRepo "http://repository.codehaus.org" + //mavenRepo "http://download.java.net/maven/2/" + //mavenRepo "http://repository.jboss.com/maven2/" } dependencies { - //runtime "commons-net:commons-net:3.0.1" - //runtime "net.oauth:oauth:1.0.1" + // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes e.g. + // runtime 'mysql:mysql-connector-java:5.1.27' + // runtime 'org.postgresql:postgresql:9.3-1100-jdbc41' } plugins { // plugins for the build system only build ":tomcat:7.0.50.1" + // plugins for the compile step + compile ":scaffolding:2.0.2" + compile ':cache:1.1.1' + + // plugins needed at runtime but not for compilation + runtime ":hibernate:3.6.10.8" // or ":hibernate4:4.3.1.1" runtime ":database-migration:1.3.8" runtime ":jquery:1.11.0" runtime ":resources:1.2.1" + // Uncomment these (or add new ones) to enable additional resources capabilities + //runtime ":zipped-resources:1.0.1" + //runtime ":cached-resources:1.1" + //runtime ":yui-minify-resources:0.1.5" runtime ':twitter-bootstrap:3.1.1' + + // An alternative to the default resources plugin is the asset-pipeline plugin + //compile ":asset-pipeline:1.5.0" + + // Uncomment these to enable additional asset-pipeline capabilities + //compile ":sass-asset-pipeline:1.5.1" + //compile ":less-asset-pipeline:1.5.0" + //compile ":coffee-asset-pipeline:1.5.0" + //compile ":handlebars-asset-pipeline:1.0.0.3" } } diff --git a/bbb-lti/grails-app/conf/Config.groovy b/bbb-lti/grails-app/conf/Config.groovy index b276a78363..8b1c701b29 100644 --- a/bbb-lti/grails-app/conf/Config.groovy +++ b/bbb-lti/grails-app/conf/Config.groovy @@ -1,139 +1,134 @@ -/* - 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 . -*/ - -// locations to search for config files that get merged into the main config; -// config files can be ConfigSlurper scripts, Java properties files, or classes -// in the classpath in ConfigSlurper format - -grails.config.locations = [ "classpath:lti.properties"] -// grails.config.locations = [ "classpath:${appName}-config.properties", -// "classpath:${appName}-config.groovy", -// "file:${userHome}/.grails/${appName}-config.properties", -// "file:${userHome}/.grails/${appName}-config.groovy"] - -// if (System.properties["${appName}.config.location"]) { -// grails.config.locations << "file:" + System.properties["${appName}.config.location"] -// } - -grails.project.groupId = appName // change this to alter the default package name and Maven publishing destination - -grails.mime.file.extensions = true // enables the parsing of file extensions from URLs into the request format -grails.mime.use.accept.header = false -// The ACCEPT header will not be used for content negotiation for user agents containing the following strings (defaults to the 4 major rendering engines) -grails.mime.disable.accept.header.userAgents = ['Gecko', 'WebKit', 'Presto', 'Trident'] -grails.mime.types = [ // the first one is the default format - all: '*/*', // 'all' maps to '*' or the first available format in withFormat - atom: 'application/atom+xml', - css: 'text/css', - csv: 'text/csv', - form: 'application/x-www-form-urlencoded', - html: ['text/html','application/xhtml+xml'], - js: 'text/javascript', - json: ['application/json', 'text/json'], - multipartForm: 'multipart/form-data', - rss: 'application/rss+xml', - text: 'text/plain', - hal: ['application/hal+json','application/hal+xml'], - xml: ['text/xml', 'application/xml'] -] - -// URL Mapping Cache Max Size, defaults to 5000 -//grails.urlmapping.cache.maxsize = 1000 - -// What URL patterns should be processed by the resources plugin -grails.resources.adhoc.patterns = ['/images/*', '/css/*', '/js/*', '/plugins/*'] -grails.resources.adhoc.excludes = ['/WEB-INF/**'] - -// Legacy setting for codec used to encode data with ${} -grails.views.default.codec = "html" - -// The default scope for controllers. May be prototype, session or singleton. -// If unspecified, controllers are prototype scoped. -grails.controllers.defaultScope = 'singleton' - -// GSP settings -grails { - views { - gsp { - encoding = 'UTF-8' - htmlcodec = 'xml' // use xml escaping instead of HTML4 escaping - codecs { - expression = 'html' // escapes values inside ${} - scriptlet = 'html' // escapes output from scriptlets in GSPs - taglib = 'none' // escapes output from taglibs - staticparts = 'none' // escapes output from static template parts - } - } - // escapes all not-encoded output at final stage of outputting - // filteringCodecForContentType.'text/html' = 'html' - } -} - - -grails.converters.encoding = "UTF-8" -// scaffolding templates configuration -grails.scaffolding.templates.domainSuffix = 'Instance' - -// Set to false to use the new Grails 1.2 JSONBuilder in the render method -grails.json.legacy.builder = false -// enabled native2ascii conversion of i18n properties files -grails.enable.native2ascii = true -// packages to include in Spring bean scanning -grails.spring.bean.packages = [] -// whether to disable processing of multi part requests -grails.web.disable.multipart=false - -// request parameters to mask when logging exceptions -grails.exceptionresolver.params.exclude = ['password'] - -// configure auto-caching of queries by default (if false you can cache individual queries with 'cache: true') -grails.hibernate.cache.queries = false - -environments { - development { - grails.logging.jul.usebridge = true - } - production { - grails.logging.jul.usebridge = false - } -} - -// log4j configuration -log4j = { - appenders { - rollingFile name:"logfile", maxFileSize:1000000, file:"/var/log/bigbluebutton/bbb-lti.log", layout:pattern(conversionPattern: '%d{[dd.MM.yy HH:mm:ss.SSS]} %-5p %c %x - %m%n') - console name:'console', layout:pattern(conversionPattern: '%d{[dd.MM.yy HH:mm:ss.SSS]} %-5p %c %x - %m%n') - 'null' name:'stacktrace' - } - debug logfile:"grails.app" - - error 'org.codehaus.groovy.grails.web.servlet', // controllers - 'org.codehaus.groovy.grails.web.pages', // GSP - 'org.codehaus.groovy.grails.web.sitemesh', // layouts - 'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping - 'org.codehaus.groovy.grails.web.mapping', // URL mapping - 'org.codehaus.groovy.grails.commons', // core / classloading - 'org.codehaus.groovy.grails.plugins', // plugins - 'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration - 'org.springframework', - 'org.hibernate', - 'net.sf.ehcache.hibernate' - - warn 'org.mortbay.log' - -} +/* + 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 . +*/ + +// locations to search for config files that get merged into the main config; +// config files can be ConfigSlurper scripts, Java properties files, or classes +// in the classpath in ConfigSlurper format + +grails.config.locations = [ "classpath:lti.properties"] +// grails.config.locations = [ "classpath:${appName}-config.properties", +// "classpath:${appName}-config.groovy", +// "file:${userHome}/.grails/${appName}-config.properties", +// "file:${userHome}/.grails/${appName}-config.groovy"] + +// if (System.properties["${appName}.config.location"]) { +// grails.config.locations << "file:" + System.properties["${appName}.config.location"] +// } + +grails.project.groupId = appName // change this to alter the default package name and Maven publishing destination + +// The ACCEPT header will not be used for content negotiation for user agents containing the following strings (defaults to the 4 major rendering engines) +grails.mime.disable.accept.header.userAgents = ['Gecko', 'WebKit', 'Presto', 'Trident'] +grails.mime.types = [ // the first one is the default format + all: '*/*', // 'all' maps to '*' or the first available format in withFormat + atom: 'application/atom+xml', + css: 'text/css', + csv: 'text/csv', + form: 'application/x-www-form-urlencoded', + html: ['text/html','application/xhtml+xml'], + js: 'text/javascript', + json: ['application/json', 'text/json'], + multipartForm: 'multipart/form-data', + rss: 'application/rss+xml', + text: 'text/plain', + hal: ['application/hal+json','application/hal+xml'], + xml: ['text/xml', 'application/xml'] +] + +// URL Mapping Cache Max Size, defaults to 5000 +//grails.urlmapping.cache.maxsize = 1000 + +// What URL patterns should be processed by the resources plugin +grails.resources.adhoc.patterns = ['/images/*', '/css/*', '/js/*', '/plugins/*'] +grails.resources.adhoc.excludes = ['/WEB-INF/**'] + +// Legacy setting for codec used to encode data with ${} +grails.views.default.codec = "html" + +// The default scope for controllers. May be prototype, session or singleton. +// If unspecified, controllers are prototype scoped. +grails.controllers.defaultScope = 'singleton' + +// GSP settings +grails { + views { + gsp { + encoding = 'UTF-8' + htmlcodec = 'xml' // use xml escaping instead of HTML4 escaping + codecs { + expression = 'html' // escapes values inside ${} + scriptlet = 'html' // escapes output from scriptlets in GSPs + taglib = 'none' // escapes output from taglibs + staticparts = 'none' // escapes output from static template parts + } + } + // escapes all not-encoded output at final stage of outputting + // filteringCodecForContentType.'text/html' = 'html' + } +} + + +grails.converters.encoding = "UTF-8" +// scaffolding templates configuration +grails.scaffolding.templates.domainSuffix = 'Instance' + +// Set to false to use the new Grails 1.2 JSONBuilder in the render method +grails.json.legacy.builder = false +// enabled native2ascii conversion of i18n properties files +grails.enable.native2ascii = true +// packages to include in Spring bean scanning +grails.spring.bean.packages = [] +// whether to disable processing of multi part requests +grails.web.disable.multipart=false + +// request parameters to mask when logging exceptions +grails.exceptionresolver.params.exclude = ['password'] + +// configure auto-caching of queries by default (if false you can cache individual queries with 'cache: true') +grails.hibernate.cache.queries = false + +environments { + development { + grails.logging.jul.usebridge = true + } + production { + grails.logging.jul.usebridge = false + // TODO: grails.serverURL = "http://www.changeme.com" + } +} + +// log4j configuration +log4j = { + // Example of changing the log pattern for the default console appender: + // + //appenders { + // console name:'stdout', layout:pattern(conversionPattern: '%c{2} %m%n') + //} + + error 'org.codehaus.groovy.grails.web.servlet', // controllers + 'org.codehaus.groovy.grails.web.pages', // GSP + 'org.codehaus.groovy.grails.web.sitemesh', // layouts + 'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping + 'org.codehaus.groovy.grails.web.mapping', // URL mapping + 'org.codehaus.groovy.grails.commons', // core / classloading + 'org.codehaus.groovy.grails.plugins', // plugins + 'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration + 'org.springframework', + 'org.hibernate', + 'net.sf.ehcache.hibernate' +} diff --git a/bbb-lti/grails-app/conf/DataSource.groovy b/bbb-lti/grails-app/conf/DataSource.groovy index f961ac941f..0a88abb5c2 100644 --- a/bbb-lti/grails-app/conf/DataSource.groovy +++ b/bbb-lti/grails-app/conf/DataSource.groovy @@ -15,7 +15,6 @@ You should have received a copy of the GNU Lesser General Public License along with BigBlueButton; if not, see . */ - dataSource { pooled = true jmxExport = true diff --git a/bbb-lti/grails-app/conf/UrlMappings.groovy b/bbb-lti/grails-app/conf/UrlMappings.groovy index 1eb88de377..beb1333239 100644 --- a/bbb-lti/grails-app/conf/UrlMappings.groovy +++ b/bbb-lti/grails-app/conf/UrlMappings.groovy @@ -15,11 +15,10 @@ You should have received a copy of the GNU Lesser General Public License along with BigBlueButton; if not, see . */ - class UrlMappings { static mappings = { - "/$controller/$action?/$id"{ + "/$controller/$action?/$id?(.$format)?"{ constraints { // apply constraints here } diff --git a/bbb-lti/grails-app/conf/lti.properties b/bbb-lti/grails-app/conf/lti.properties index 1580d2f149..830ecfb8a1 100644 --- a/bbb-lti/grails-app/conf/lti.properties +++ b/bbb-lti/grails-app/conf/lti.properties @@ -21,8 +21,10 @@ # BigBlueButton integration information #---------------------------------------------------- # This URL is where the BBB client is accessible. +#bigbluebuttonURL=http://test-install.blindsidenetworks.com/bigbluebutton bigbluebuttonURL=http://localhost/bigbluebutton # Salt which is used by 3rd-party apps to authenticate api calls +#bigbluebuttonSalt=8cd8ef52e8e101574e400365b55e11a6 bigbluebuttonSalt=bbb_salt # LTI basic information diff --git a/bbb-lti/grails-app/controllers/ToolController.groovy b/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy similarity index 84% rename from bbb-lti/grails-app/controllers/ToolController.groovy rename to bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy index bf03b7a6a6..b4ccbac148 100644 --- a/bbb-lti/grails-app/controllers/ToolController.groovy +++ b/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy @@ -15,6 +15,7 @@ You should have received a copy of the GNU Lesser General Public License along with BigBlueButton; if not, see . */ +package org.bigbluebutton import java.util.ArrayList import java.util.HashMap @@ -29,27 +30,29 @@ import net.oauth.signature.OAuthSignatureMethod import net.oauth.signature.HMAC_SHA1 import org.bigbluebutton.lti.Parameter -import BigbluebuttonService -import LtiService - class ToolController { private static final String CONTROLLER_NAME = 'ToolController' private static final String RESP_CODE_SUCCESS = 'SUCCESS' private static final String RESP_CODE_FAILED = 'FAILED' private static final String REQUEST_METHOD = "request_method"; - + LtiService ltiService BigbluebuttonService bigbluebuttonService - - def index = { + + def test() { + ltiService.logParameters(params) + render(text: "", contentType: "text/xml", encoding: "UTF-8") + } + + def index() { if( ltiService.consumerMap == null) ltiService.initConsumerMap() log.debug CONTROLLER_NAME + "#index" setLocalization(params) - + params.put(REQUEST_METHOD, request.getMethod().toUpperCase()) ltiService.logParameters(params) - + if( request.post ){ Map result = new HashMap() ArrayList missingParams = new ArrayList() @@ -62,7 +65,7 @@ class ToolController { log.debug "Found consumer with key " + consumer.get("key") //+ " and sharedSecret " + consumer.get("secret") if (checkValidSignature(params.get(REQUEST_METHOD), ltiService.endPoint, consumer.get("secret"), sanitizedParams, params.get(Parameter.OAUTH_SIGNATURE))) { log.debug "The message has a valid signature." - + if( !"extended".equals(ltiService.mode) ) { log.debug "LTI service running in simple mode." result = doJoinMeeting(params) @@ -73,18 +76,18 @@ class ToolController { 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) { @@ -121,7 +124,7 @@ class ToolController { } } - def join = { + def join() { if( ltiService.consumerMap == null) ltiService.initConsumerMap() log.debug CONTROLLER_NAME + "#join" Map result @@ -136,7 +139,7 @@ class ToolController { result = new HashMap() result.put("resultMessageKey", "InvalidSession") result.put("resultMessage", "Invalid session. User can not execute this action.") - } + } if( result.containsKey("resultMessageKey")) { log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']" @@ -145,7 +148,7 @@ class ToolController { } - def publish = { + def publish() { log.debug CONTROLLER_NAME + "#publish" Map result @@ -162,10 +165,10 @@ class ToolController { } else { log.debug "params: " + params log.debug "sessionParams: " + sessionParams - + //Execute the publish command result = bigbluebuttonService.doPublishRecordings(params) - } + } if( result.containsKey("resultMessageKey")) { log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']" @@ -182,14 +185,14 @@ class ToolController { /// Add duration recording.put("duration", duration ) } - + render(view: "index", model: ['params': sessionParams, 'recordingList': recordings, 'ismoderator': bigbluebuttonService.isModerator(sessionParams)]) } } - def delete = { + def delete() { log.debug CONTROLLER_NAME + "#delete" Map result @@ -206,7 +209,7 @@ class ToolController { } else { log.debug "params: " + params log.debug "sessionParams: " + sessionParams - + //Execute the delete command result = bigbluebuttonService.doDeleteRecordings(params) } @@ -242,23 +245,23 @@ class ToolController { 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]) - + log.debug "Locale has been set to " + locale } private Object doJoinMeeting(params) { Map result = new HashMap() - + setLocalization(params) String welcome = message(code: "bigbluebutton.welcome.header", args: ["\"{0}\"", "\"{1}\""]) + "
" log.debug "Localized default welcome message: [" + welcome + "]" - // Check for [custom_]welcome parameter being passed from the LTI - if (params.get(Parameter.CUSTOM_WELCOME) != null) { - welcome = params.get(Parameter.CUSTOM_WELCOME) + "
" - log.debug "Overriding default welcome message with: [" + welcome + "]" - } + // Check for [custom_]welcome parameter being passed from the LTI + if (params.get(Parameter.CUSTOM_WELCOME) != null) { + welcome = params.get(Parameter.CUSTOM_WELCOME) + "
" + log.debug "Overriding default welcome message with: [" + welcome + "]" + } if ( Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) ) { welcome += "
" + message(code: "bigbluebutton.welcome.record") + "
" @@ -266,7 +269,9 @@ class ToolController { } if ( Integer.parseInt(params.get(Parameter.CUSTOM_DURATION)) > 0 ) { - welcome += "
" + message(code: "bigbluebutton.welcome.duration", args: [params.get(Parameter.CUSTOM_DURATION)]) + "
" + welcome += "
" + message(code: "bigbluebutton.welcome.duration", args: [ + params.get(Parameter.CUSTOM_DURATION) + ]) + "
" log.debug "Adding duration warning to welcome message, welcome is now: [" + welcome + "]" } @@ -275,24 +280,24 @@ class ToolController { 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") } - + return result } - + /** * Assemble all parameters passed that is required to sign the request. * @param the HTTP request parameters * @return the key:val pairs needed for Basic LTI */ private Properties sanitizePrametersForBaseString(Object params) { - + Properties reqProp = new Properties(); for (String key : ((Map)params).keySet()) { if (key == "action" || key == "controller") { @@ -348,11 +353,11 @@ class ToolController { * @return - TRUE if the signatures matches the calculated signature */ private boolean checkValidSignature(String method, String URL, String conSecret, Object postProp, String signature) { - log.debug( "Starting checkValidSignature()" ) + log.debug( "Starting checkValidSignature()" ) OAuthMessage oam = new OAuthMessage(method, URL, ((Properties)postProp).entrySet()) - log.debug( "OAuthMessage oam = " + oam.toString() ) + log.debug( "OAuthMessage oam = " + oam.toString() ) HMAC_SHA1 hmac = new HMAC_SHA1() - log.debug( "HMAC_SHA1 hmac = " + hmac.toString() ) + log.debug( "HMAC_SHA1 hmac = " + hmac.toString() ) hmac.setConsumerSecret(conSecret) log.debug("Base Message String = [ " + hmac.getBaseString(oam) + " ]\n") @@ -360,36 +365,33 @@ class ToolController { log.debug("Calculated: " + calculatedSignature + " Received: " + signature) return calculatedSignature.equals(signature) } - + private String getCartridgeXML(){ def cartridge = '' + - '' + - '' + - ' BigBlueButton' + - ' Single Sign On into BigBlueButton' + - ' ' + ltiService.retrieveBasicLtiEndpoint() + '' + - ' ' + ltiService.retrieveIconEndpoint() + '' + - ' ' + - ' BBB' + - ' BigBlueButton' + - ' Open source web conferencing system for distance learning.' + - ' http://www.bigbluebutton.org/' + - ' ' + - ' ' + - ' ' + - '' - + '' + + '' + + ' BigBlueButton' + + ' Single Sign On into BigBlueButton' + + ' ' + ltiService.retrieveBasicLtiEndpoint() + '' + + ' ' + ltiService.retrieveIconEndpoint() + '' + + ' ' + + ' BBB' + + ' BigBlueButton' + + ' Open source web conferencing system for distance learning.' + + ' http://www.bigbluebutton.org/' + + ' ' + + ' ' + + ' ' + + '' + return cartridge - } - - } diff --git a/bbb-lti/grails-app/services/BigbluebuttonService.groovy b/bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy similarity index 91% rename from bbb-lti/grails-app/services/BigbluebuttonService.groovy rename to bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy index 8bab7e2400..63a9c9c2c7 100644 --- a/bbb-lti/grails-app/services/BigbluebuttonService.groovy +++ b/bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy @@ -15,40 +15,42 @@ You should have received a copy of the GNU Lesser General Public License along with BigBlueButton; if not, see . */ +package org.bigbluebutton -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.StringReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader +import java.io.StringReader +import java.net.HttpURLConnection +import java.net.URL +import java.text.MessageFormat +import java.util.ArrayList +import java.util.HashMap +import java.util.List +import java.util.Map +import java.util.Random -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.DocumentBuilder +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.parsers.ParserConfigurationException -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; +import org.w3c.dom.Document +import org.w3c.dom.Node +import org.w3c.dom.NodeList +import org.xml.sax.InputSource +import org.xml.sax.SAXException -import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.codec.digest.DigestUtils import org.bigbluebutton.api.Proxy import org.bigbluebutton.lti.Role import org.bigbluebutton.lti.Parameter +import grails.transaction.Transactional + +@Transactional class BigbluebuttonService { - boolean transactional = false - def url = "http://test-install.blindsidenetworks.com/bigbluebutton" def salt = "8cd8ef52e8e101574e400365b55e11a6" @@ -64,19 +66,19 @@ class BigbluebuttonService { } catch (ParserConfigurationException e) { logger.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 - + 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 +88,7 @@ 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,12 +97,12 @@ class BigbluebuttonService { record = getValidatedBBBRecord(params.get(Parameter.CUSTOM_RECORD)) duration = getValidatedBBBDuration(params.get(Parameter.CUSTOM_DURATION)) } - + 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 createResponse = doAPICall(createURL) @@ -110,22 +112,21 @@ class BigbluebuttonService { 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? moderatorPW: attendeePW, (String) createResponse.get("createTime"), userID); + (Proxy.APIRESPONSE_FAILED.equals(returnCode) && (Proxy.MESSAGEKEY_IDNOTUNIQUE.equals(messageKey) || Proxy.MESSAGEKEY_DUPLICATEWARNING.equals(messageKey)) ) ){ + joinURL = bbbProxy.getJoinURL( userFullName, meetingID, isModerator? moderatorPW: attendeePW, (String) createResponse.get("createTime"), userID); } } 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) String meetingID = getValidatedMeetingId(params.get(Parameter.RESOURCE_LINK_ID), params.get(Parameter.CONSUMER_ID)) - + String recordingsURL = bbbProxy.getGetRecordingsURL( meetingID ) log.debug "recordingsURL: " + recordingsURL Map recordings = doAPICall(recordingsURL) @@ -135,7 +136,7 @@ class BigbluebuttonService { String messageKey = (String) recordings.get("messageKey") if ( Proxy.APIRESPONSE_SUCCESS.equals(returnCode) && messageKey == null ){ return recordings.get("recordings") - } + } } return null @@ -147,9 +148,9 @@ class BigbluebuttonService { if( !salt.equals(bbbProxy.salt) && !salt.equals("") ) bbbProxy.setSalt(salt) Map result - + String recordingId = getValidatedBBBRecordingId(params.get(Parameter.BBB_RECORDING_ID)) - + if( !recordingId.equals("") ){ String deleteRecordingsURL = bbbProxy.getDeleteRecordingsURL( recordingId ) log.debug "deleteRecordingsURL: " + deleteRecordingsURL @@ -162,17 +163,17 @@ class BigbluebuttonService { 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 result 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 @@ -185,23 +186,23 @@ class BigbluebuttonService { return result } - + public boolean isModerator(params) { boolean isModerator = params.get(Parameter.ROLES) != null? Role.isModerator(params.get(Parameter.ROLES)): true return isModerator } - + private String getCreateURL(String name, String meetingID, String attendeePW, String moderatorPW, String welcome, Integer voiceBridge, String logoutURL, String record, Integer duration, String meta ) { voiceBridge = ( voiceBridge == null || voiceBridge == 0 )? 70000 + new Random(System.currentTimeMillis()).nextInt(10000): voiceBridge; String url = bbbProxy.getCreateURL(name, meetingID, attendeePW, moderatorPW, welcome, "", voiceBridge.toString(), "", logoutURL, "", record, duration.toString(), meta ); return url; } - + private String getValidatedMeetingName(String meetingName){ return (meetingName == null || meetingName == "")? "Meeting": meetingName } - + private String getValidatedMeetingId(String resourceId, String consumerId){ return DigestUtils.shaHex(resourceId + consumerId) } @@ -209,7 +210,7 @@ class BigbluebuttonService { private String getValidatedLogoutURL(String logoutURL){ return (logoutURL == null)? "": logoutURL } - + private String getValidatedUserFullName(params, boolean isModerator){ String userFullName = params.get(Parameter.USER_FULL_NAME) String userFirstName = params.get(Parameter.USER_FIRSTNAME) @@ -226,7 +227,7 @@ class BigbluebuttonService { userFullName = isModerator? "Moderator" : "Attendee" } } - + return userFullName } @@ -237,15 +238,15 @@ class BigbluebuttonService { private String getValidatedUserId(String userId){ return (userId == null)? "": userId } - + private Integer getValidatedBBBVoiceBridge(String voiceBridge){ return (voiceBridge != null )? voiceBridge.toInteger(): 0 } - + private String getValidatedBBBRecord(String record){ return Boolean.valueOf(record).toString(); } - + private Integer getValidatedBBBDuration(String duration){ return (duration != null )? duration.toInteger(): 0 } @@ -257,7 +258,7 @@ class BigbluebuttonService { private String getValidatedBBBRecordingPublished(String published){ return (published != null && published.equals("true") )? "true": "false" } - + private String getMonitoringMetaData(params){ String meta @@ -269,10 +270,10 @@ 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 doAPICall(String query) { StringBuilder urlStr = new StringBuilder(query); @@ -280,7 +281,7 @@ class BigbluebuttonService { try { // open connection //log.debug("doAPICall.call: " + query ); - + URL url = new URL(urlStr.toString()); HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection(); httpConnection.setUseCaches(false); @@ -300,14 +301,16 @@ class BigbluebuttonService { String line = reader.readLine(); while (line != null) { if( !line.startsWith("")) - xml.append(line.trim()); + xml.append(line.trim()); line = reader.readLine(); } } finally { - if (reader != null) - reader.close(); - if (isr != null) - isr.close(); + if ( reader != null ) { + reader.close() + } + if ( isr != null ) { + isr.close() + } } httpConnection.disconnect(); @@ -316,13 +319,13 @@ class BigbluebuttonService { //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 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")); @@ -344,7 +347,7 @@ class BigbluebuttonService { log.debug("doAPICall.Exception: Message=" + e.getMessage()); } } - + /** Get all nodes under the specified element tag name as a Java map */ protected Map getNodesAsMap(Document dom, String elementTagName) { Node firstNode = dom.getElementsByTagName(elementTagName).item(0); @@ -361,12 +364,12 @@ class BigbluebuttonService { && ( 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 ) { @@ -377,12 +380,11 @@ class BigbluebuttonService { list.add(processNode(n)); } map.put(nodeName, list); - + } else { map.put(nodeName, processNode(node)); } } return map; } - } diff --git a/bbb-lti/grails-app/services/LtiService.groovy b/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy similarity index 84% rename from bbb-lti/grails-app/services/LtiService.groovy rename to bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy index 4072dc9231..8254cba654 100644 --- a/bbb-lti/grails-app/services/LtiService.groovy +++ b/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy @@ -15,23 +15,23 @@ You should have received a copy of the GNU Lesser General Public License along with BigBlueButton; if not, see . */ +package org.bigbluebutton -import java.util.Map; - +import grails.transaction.Transactional +import java.util.Map import javax.crypto.spec.SecretKeySpec import javax.crypto.Mac import org.apache.commons.codec.binary.Base64 +@Transactional class LtiService { - boolean transactional = false - def endPoint = "http://localhost/lti/tool" def consumers = "demo:welcome" def mode = "simple" Map consumerMap - + def retrieveIconEndpoint() { return endPoint.replaceFirst("tool", "images/icon.ico") } @@ -39,16 +39,16 @@ class LtiService { def retrieveBasicLtiEndpoint() { return endPoint } - + private Map getConsumer(consumerId) { Map consumer = null - + if( this.consumerMap.containsKey(consumerId) ){ consumer = new HashMap() - consumer.put("key", consumerId); + consumer.put("key", consumerId) consumer.put("secret", this.consumerMap.get(consumerId)) } - + return consumer } @@ -63,25 +63,22 @@ class LtiService { 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); + byte[] signBytes = mac.doFinal(data.getBytes("UTF8")) + String signature = encodeBase64(signBytes) return signature; } - - private Mac setKey(String sharedSecret) throws Exception - { - Mac mac = Mac.getInstance("HmacSHA1"); - byte[] keyBytes = sharedSecret.getBytes("UTF8"); - SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1"); - mac.init(signingKey); + + private Mac setKey(String sharedSecret) throws Exception { + Mac mac = Mac.getInstance("HmacSHA1") + byte[] keyBytes = sharedSecret.getBytes("UTF8") + SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1") + mac.init(signingKey) return mac } diff --git a/bbb-lti/grails-app/views/index.gsp b/bbb-lti/grails-app/views/index.gsp index ee53f42808..cf4c0b438b 100644 --- a/bbb-lti/grails-app/views/index.gsp +++ b/bbb-lti/grails-app/views/index.gsp @@ -81,7 +81,6 @@ -