Merge pull request #105 from jfederico/master

bbb-lti_v0.1.1.
This commit is contained in:
Fred Dixon 2013-03-13 08:50:58 -07:00
commit 091ba72f0c
35 changed files with 6627 additions and 174 deletions

7
.gitignore vendored
View File

@ -5,3 +5,10 @@ bigbluebutton-client/.actionScriptProperties
bigbluebutton-client/.flexProperties
push_to_git.py
*/.gradle
.gitignore
bbb-lti/.classpath
bbb-lti/.project
bbb-lti/bin
bbb-lti/lti-0.1.1.war
bbb-lti/make.sh
bbb-lti/deploy.sh

View File

@ -1,6 +1,6 @@
#utf-8
#Wed Oct 10 08:34:02 PDT 2012
app.version=0.1
app.version=0.1.1
app.servlet.version=2.4
app.grails.version=1.1.1
plugins.hibernate=1.1.1

View File

@ -87,6 +87,7 @@ log4j = {
warn 'org.mortbay.log'
'null' name:'stacktrace'
}

View File

@ -21,17 +21,23 @@
# BigBlueButton integration information
#----------------------------------------------------
# This URL is where the BBB client is accessible.
bigbluebuttonURL=http://localhost/bigbluebutton
#bigbluebuttonURL=http://localhost/bigbluebutton
bigbluebuttonURL=http://192.168.0.153/bigbluebutton
# Salt which is used by 3rd-party apps to authenticate api calls
bigbluebuttonSalt=bbb_salt
#bigbluebuttonSalt=bbb_salt
bigbluebuttonSalt=e1f2f284119d5754cef6c80ba1e2f393
# LTI basic information
#----------------------------------------------------
# This URL is where the LTI plugin is accessible. It can be a different server than the BigBluebutton one
ltiEndPoint=http://localhost/lti/tool.xml
#ltiEndPoint=http://localhost/lti/tool
ltiEndPoint=http://192.168.0.153/lti/tool
# The list of consumers allowed to access this lti service.
# Format: [consumerId:sharedSecret]
# Format: {consumerId1:sharedSecret1}[,consumerId2:sharedSecret2]
ltiConsumers=bbb:lti_secret
# The mode used to interact with BigBlueButton
# Format: [<simple>|extended]
ltiMode=extended
#----------------------------------------------------
# Inject configuration values into BigbluebuttonSrvice beans
@ -42,4 +48,5 @@ beans.bigbluebuttonService.salt=${bigbluebuttonSalt}
# Inject configuration values into LtiSrvice beans
beans.ltiService.endPoint=${ltiEndPoint}
beans.ltiService.consumers=${ltiConsumers}
beans.ltiService.mode=${ltiMode}

View File

@ -36,134 +36,219 @@ 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 = {
if( ltiService.consumerMap == null) ltiService.initConsumerMap()
log.debug CONTROLLER_NAME + "#index" + ltiService.consumerMap
log.debug params
log.debug CONTROLLER_NAME + "#index"
def resultMessageKey = "init"
def resultMessage = "init"
def success = false
def consumer
params.put(REQUEST_METHOD, request.getMethod().toUpperCase())
log.debug "params: " + params
Map<String, String> result = new HashMap<String, String>()
ArrayList<String> missingParams = new ArrayList<String>()
log.debug "Checking for required parameters"
if (hasAllRequiredParams(params, missingParams)) {
def sanitizedParams = sanitizePrametersForBaseString(params)
consumer = ltiService.getConsumer(params.get(Parameter.CONSUMER_ID))
def consumer = ltiService.getConsumer(params.get(Parameter.CONSUMER_ID))
if (consumer != null) {
log.debug "Found consumer with key " + consumer.get("key") //+ " and sharedSecret " + consumer.get("secret")
if (checkValidSignature(request.getMethod().toUpperCase(), retrieveLtiEndpoint(), consumer.get("secret"), sanitizedParams, params.get(Parameter.OAUTH_SIGNATURE))) {
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."
String locale = params.get(Parameter.LAUNCH_LOCALE)
locale = (locale == null || locale.equals("")?"en":locale)
log.debug "Locale code =" + locale
String[] localeCodes = locale.split("_")
//Localize the default welcome message
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])
log.debug "Locale has been set to " + locale
String welcome = message(code: "bigbluebutton.welcome", 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) {
log.debug "A custom welcome message has been provided"
welcome = params.get(Parameter.CUSTOM_WELCOME)
log.debug "Overriding default welcome message with: [" + welcome + "]"
}
// Detect if the LTI has requested recording
if (params.get(Parameter.CUSTOM_RECORD) == "true") {
log.debug "This session will be recorded!"
}
//String destinationURL = "http://www.bigbluebutton.org/"
String destinationURL = bigbluebuttonService.getJoinURL(params, welcome)
log.debug "redirecting to " + destinationURL
if( destinationURL != null ) {
success = true
redirect(url:destinationURL)
if( !"extended".equals(ltiService.mode) ) {
log.debug "LTI service running in simple mode."
result = doJoinMeeting(params)
} else {
resultMessageKey = 'BigBlueButtonServerError'
resultMessage = "The join could not be completed"
log.debug resultMessage
log.debug "LTI service running in extended mode."
}
} else {
resultMessageKey = 'InvalidSignature'
resultMessage = "Invalid signature (" + params.get(Parameter.OAUTH_SIGNATURE) + ")."
log.debug resultMessage
result.put("resultMessageKey", "InvalidSignature")
result.put("resultMessage", "Invalid signature (" + params.get(Parameter.OAUTH_SIGNATURE) + ").")
}
} else {
resultMessageKey = 'CustomerNotFound'
resultMessage = "Customer with id = " + params.get(Parameter.CONSUMER_ID) + " was not found."
log.debug resultMessage
result.put("resultMessageKey", "ConsumerNotFound")
result.put("resultMessage", "Consumer with id = " + params.get(Parameter.CONSUMER_ID) + " was not found.")
}
} else {
resultMessageKey = 'MissingRequiredParameter'
String missingStr = ""
for(String str:missingParams)
for(String str:missingParams) {
missingStr += str + ", ";
resultMessage = "Missing parameters [$missingStr]"
log.debug resultMessage
}
if (!success) {
log.debug "Error"
response.addHeader("Cache-Control", "no-cache")
withFormat {
xml {
render(contentType:"text/xml") {
response() {
returncode(success)
messageKey(resultMessageKey)
message(resultMessage)
}
}
}
}
}
result.put("resultMessageKey", "MissingRequiredParameter")
result.put("resultMessage", "Missing parameters [$missingStr]")
}
if( result != null && 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
List<Object> recordings = bigbluebuttonService.getRecordings(params)
render(view: "index", model: ['params': params, 'recordingList': recordings, 'ismoderator': bigbluebuttonService.isModerator(params)])
}
}
def test = {
log.debug CONTROLLER_NAME + "#index"
def view = {
if( ltiService.consumerMap == null) ltiService.initConsumerMap()
log.debug CONTROLLER_NAME + "#view" + ltiService.consumerMap
Map<String, String> result
response.addHeader("Cache-Control", "no-cache")
withFormat {
xml {
render(contentType:"text/xml") {
response() {
returncode(false)
messageKey('RequestInvalid')
message('The request is not supported.')
}
}
}
def sessionParams = session["params"]
log.debug "params: " + params
log.debug "sessionParams: " + sessionParams
if( sessionParams == null ) {
result = new HashMap<String, String>()
result.put("resultMessageKey", "InvalidSession")
result.put("resultMessage", "Session is invalid user cannot execute this action.")
} else if( !"extended".equals(ltiService.mode) ){
result = new HashMap<String, String>()
result.put("resultMessageKey", "SimpleMode")
result.put("resultMessage", "LTI service running in simple mode.")
}
if( result != null ) {
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
render(view: "error", model: ['resultMessageKey': result.get("resultMessageKey"), 'resultMessage': result.get("resultMessage")])
} else {
List<Object> recordings = bigbluebuttonService.getRecordings(sessionParams)
render(view: "index", model: ['params': params, 'recordingList': recordings, 'ismoderator':bigbluebuttonService.isModerator(sessionParams)])
}
}
def join = {
if( ltiService.consumerMap == null) ltiService.initConsumerMap()
log.debug CONTROLLER_NAME + "#join"
Map<String, String> 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.")
}
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")])
}
}
private String retrieveLtiEndpoint() {
String endPoint = ltiService.endPoint
return endPoint
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.")
} else if ( !bigbluebuttonService.isModerator(sessionParams) ) {
result = new HashMap<String, String>()
result.put("resultMessageKey", "NotAllowed")
result.put("resultMessage", "User not allowed to execute this action.")
} 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") + "']"
render(view: "error", model: ['resultMessageKey': result.get("resultMessageKey"), 'resultMessage': result.get("resultMessage")])
} else {
//String destinationURL = createLink(controller:"tool", action:"view", params:"[foo: 'bar', boo: 'far']")
String destinationURL = createLink(controller:"tool", action:"view")
log.debug "destinationURL=[" + destinationURL + "]"
redirect(url:destinationURL)
}
}
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.")
} else if ( !bigbluebuttonService.isModerator(sessionParams) ) {
result = new HashMap<String, String>()
result.put("resultMessageKey", "NotAllowed")
result.put("resultMessage", "User not allowed to execute this action.")
} else {
log.debug "params: " + params
log.debug "sessionParams: " + sessionParams
//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")])
} else {
//String destinationURL = createLink(controller:"tool", action:"view", params:"[foo: 'bar', boo: 'far']")
String destinationURL = createLink(controller:"tool", action:"view")
log.debug "destinationURL=[" + destinationURL + "]"
redirect(url:destinationURL)
}
}
private Object doJoinMeeting(params) {
Map<String, String> result = new HashMap<String, String>()
String locale = params.get(Parameter.LAUNCH_LOCALE)
locale = (locale == null || locale.equals("")?"en":locale)
log.debug "Locale code =" + locale
String[] localeCodes = locale.split("_")
//Localize the default welcome message
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])
log.debug "Locale has been set to " + locale
String welcome = message(code: "bigbluebutton.welcome", args: ["\"{0}\"", "\"{1}\""])
log.debug "Localized default welcome message: [" + welcome + "]"
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
@ -179,6 +264,9 @@ class ToolController {
} else 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
continue
}
reqProp.setProperty(key, ((Map<String, String>)params).get(key));
@ -205,6 +293,11 @@ class ToolController {
hasAllParams = false;
}
if (! ((Map<String, String>)params).containsKey(Parameter.RESOURCE_LINK_ID)) {
((ArrayList<String>)missingParams).add(Parameter.RESOURCE_LINK_ID);
hasAllParams = false;
}
return hasAllParams
}

View File

@ -17,3 +17,4 @@
#
bigbluebutton.welcome=<br>Welcome to <b>{0}</b>!<br><br>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.</b>
bigbluebutton.join=Join Meeting

View File

@ -17,3 +17,4 @@
#
bigbluebutton.welcome=<br>Bienvenido a <b>{0}</b>!<br><br>Para entender como funciona BigBlueButton consulte estos <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>videos tutoriales</u></a>.<br><br>Para activar el audio haga click en el icono de auricular (equina superior izquierda). <b>Por favor utilice auricular para evitar causar ruido.</b>
bigbluebutton.join=Ingresar a la sesión

View File

@ -17,3 +17,4 @@
#
bigbluebutton.welcome=<br>Bienvenue au <b>{0}</b>!<br><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>
bigbluebutton.join=Saisie de la réunion

View File

@ -70,7 +70,7 @@ class BigbluebuttonService {
}
public String getJoinURL(params, welcome){
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)
@ -82,28 +82,36 @@ class BigbluebuttonService {
String attendeePW = DigestUtils.shaHex("ap" + params.get(Parameter.RESOURCE_LINK_ID) + params.get(Parameter.CONSUMER_ID))
String moderatorPW = DigestUtils.shaHex("mp" + params.get(Parameter.RESOURCE_LINK_ID) + params.get(Parameter.CONSUMER_ID))
String logoutURL = getValidatedLogoutURL(params.get(Parameter.LAUNCH_RETURN_URL))
boolean isModerator = params.get(Parameter.ROLES) != null? Role.isModerator(params.get(Parameter.ROLES)): true
boolean isModerator = isModerator(params)
String userFullName = getValidatedUserFullName(params, isModerator)
String courseTitle = getValidatedCourseTitle(params.get(Parameter.COURSE_TITLE))
String userID = getValidatedUserId(params.get(Parameter.USER_ID))
String record = getValidatedRecord(params.get(Parameter.CUSTOM_RECORD))
Integer voiceBridge = 0
Boolean record = false
Integer duration = 0
if( "extended".equals(mode) ){
voiceBridge = getValidatedBBBVoiceBridge(params.get(Parameter.CUSTOM_BBB_VOICEBRIDGE))
record = getValidatedBBBRecord(params.get(Parameter.CUSTOM_BBB_RECORD))
duration = getValidatedBBBDuration(params.get(Parameter.CUSTOM_BBB_DURATION))
}
String[] values = [meetingName, courseTitle]
String welcomeMsg = MessageFormat.format(welcome, values)
String meta = getMonitoringMetaData(params)
String createURL = getCreateURL( meetingName, meetingID, attendeePW, moderatorPW, welcomeMsg, logoutURL, record, meta )
//log.debug "createURL: " + createURL
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
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.getJoinMeetingURL( userFullName, meetingID, isModerator? moderatorPW: attendeePW, (String) createResponse.get("createTime"), userID);
joinURL = bbbProxy.getJoinURL( userFullName, meetingID, isModerator? moderatorPW: attendeePW, (String) createResponse.get("createTime"), userID);
}
}
@ -111,10 +119,82 @@ class BigbluebuttonService {
}
private String getCreateURL(String name, String meetingID, String attendeePW, String moderatorPW, String welcome, String logoutURL, String record, String meta ) {
Integer voiceBridge = 70000 + new Random(System.currentTimeMillis()).nextInt(10000);
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 url = bbbProxy.getCreateURL(name, meetingID, attendeePW, moderatorPW, welcome, "", voiceBridge.toString(), "", logoutURL, "", record, "", meta );
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")
}
}
return null
}
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
String recordingId = getValidatedBBBRecordingId(params.get(Parameter.BBB_RECORDING_ID))
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 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
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 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, Boolean 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.toString(), duration.toString(), meta );
return url;
}
@ -122,8 +202,8 @@ class BigbluebuttonService {
return (meetingName == null || meetingName == "")? "Meeting": meetingName
}
private String getValidatedMeetingId(String meetingId, String consumerId){
return DigestUtils.shaHex(meetingId + consumerId)
private String getValidatedMeetingId(String resourceId, String consumerId){
return DigestUtils.shaHex(resourceId + consumerId)
}
private String getValidatedLogoutURL(String logoutURL){
@ -157,10 +237,26 @@ class BigbluebuttonService {
private String getValidatedUserId(String userId){
return (userId == null)? "": userId
}
private String getValidatedRecord(String record){
return (record != "true")? "": record
}
private Integer getValidatedBBBVoiceBridge(String voiceBridge){
return (voiceBridge != null )? voiceBridge.toInteger(): 0
}
private Boolean getValidatedBBBRecord(String record){
return (record != null && record == "true")? true: false
}
private Integer getValidatedBBBDuration(String duration){
return (duration != null )? duration.toInteger(): 0
}
private String getValidatedBBBRecordingId(String recordingId){
return (recordingId != null )? recordingId: ""
}
private String getValidatedBBBRecordingPublished(String published){
return (published != null && published.equals("true") )? "true": "false"
}
private String getMonitoringMetaData(params){
String meta
@ -250,41 +346,40 @@ class BigbluebuttonService {
}
/** Get all nodes under the specified element tag name as a Java map */
private Map<String, Object> getNodesAsMap(Document dom, String elementTagName) {
protected Map<String, Object> getNodesAsMap(Document dom, String elementTagName) {
Node firstNode = dom.getElementsByTagName(elementTagName).item(0);
return processNode(firstNode);
}
private Map<String, Object> processNode(Node _node) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
protected Map<String, Object> processNode(Node _node) {
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) {
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) {
} 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( !map.containsKey(nodeName) ) {
map.put(nodeName, processNode(node));
} 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 ) {
} else {
Object curObject = map.get(nodeName);
List<Object> list;
if( curObject.getClass().equals(LinkedHashMap.class) ){
list = new LinkedList<Object>();
list.add(curObject);
list.add(processNode(node));
map.remove(nodeName);
map.put(nodeName, list);
} else {
list = (List<Object>)curObject;
list.add(processNode(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));
}
}
return map;

View File

@ -28,6 +28,7 @@ class LtiService {
def endPoint = "http://192.168.0.153/lti/tool.xml"
def consumers = "demo:welcome"
def mode = "simple"
Map<String, String> consumerMap
private Map<String, String> getConsumer(consumerId) {

View File

@ -1,6 +1,6 @@
<html>
<head>
<title>Grails Runtime Exception</title>
<title>Runtime Exception</title>
<style type="text/css">
.message {
border: 1px solid black;
@ -24,7 +24,7 @@
</head>
<body>
<h1>Grails Runtime Exception</h1>
<h1>Runtime Exception</h1>
<h2>Error Details</h2>
<div class="message">

View File

@ -1,20 +1 @@
<html>
<head>
<title>Welcome to Grails</title>
<meta name="layout" content="main" />
</head>
<body>
<h1 style="margin-left:20px;">Welcome to Grails</h1>
<p style="margin-left:20px;width:80%">Congratulations, you have successfully started your first Grails application! At the moment
this is the default page, feel free to modify it to either redirect to a controller or display whatever
content you may choose. Below is a list of controllers that are currently deployed in this application,
click on each to execute its default action:</p>
<div class="dialog" style="margin-left:20px;width:60%;">
<ul>
<g:each var="c" in="${grailsApplication.controllerClasses}">
<li class="controller"><g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link></li>
</g:each>
</ul>
</div>
</body>
</html>
<%response.sendRedirect(request.getContextPath()+"/tool");%>

View File

@ -0,0 +1,12 @@
<html>
<head>
<title><g:layoutTitle default="BigBlueButton LTI Interface" /></title>
<link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" />
<link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
<g:layoutHead />
<g:javascript library="application" />
</head>
<body>
<g:layoutBody />
</body>
</html>

View File

@ -1,6 +1,6 @@
<html>
<head>
<title><g:layoutTitle default="Grails" /></title>
<title><g:layoutTitle default="BigBlueButton LTI Interface" /></title>
<link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" />
<link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
<g:layoutHead />
@ -10,7 +10,7 @@
<div id="spinner" class="spinner" style="display:none;">
<img src="${resource(dir:'images',file:'spinner.gif')}" alt="Spinner" />
</div>
<div class="logo"><img src="${resource(dir:'images',file:'grails_logo.jpg')}" alt="Grails" /></div>
<div class="logo"><img src="${resource(dir:'images',file:'bbb_logo.jpg')}" alt="BigBlueButton" /></div>
<g:layoutBody />
</body>
</html>

View File

@ -0,0 +1,26 @@
<%@ page contentType="text/html;charset=ISO-8859-1" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<meta name="layout" content="main"/>
<title>Insert title here</title>
</head>
<body>
<div class="body">
<g:if test="${ (resultMessageKey == 'InvalidEPortfolioUserId')}">
${resultMessage}
</g:if>
<g:else>
Connection could not be established.
</g:else>
</div>
<!-- {
"error": {
"messageKey": "${resultMessageKey}",
"message": "${resultMessage}"
}
}
-->
<br/><br/>
</body>
</html>

View File

@ -0,0 +1,60 @@
<html>
<head>
<title>BigBlueButton LTI Interface</title>
<link rel="stylesheet" href="${resource(dir:'css',file:'bootstrap.css')}" />
<link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
</head>
<body>
<h1 style="margin-left:20px; text-align: center;"><a title="Join" class="btn btn-primary btn-large" href="${createLink(controller:'tool',action:'join')}">Join Meeting</a></h1>
<br><br>
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th class="header c0" style="text-align:center;" scope="col">Recording</th>
<th class="header c1" style="text-align:center;" scope="col">Activity</th>
<th class="header c2" style="text-align:center;" scope="col">Description</th>
<th class="header c3" style="text-align:center;" scope="col">Date</th>
<th class="header c4" style="text-align:center;" scope="col">Duration</th>
<g:if test="${ismoderator}">
<th class="header c5 lastcol" style="text-align:left;" scope="col">Toolbar</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:each in="${r.playback}" var="p">
<a title="${p.type}" target="_new" href="${p.url}">${p.type}</a>&#32;
</g:each>
</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;">${new Date( Long.valueOf(r.startTime).longValue() )}</td>
<td class="cell c4" style="text-align:center;">
<g:each in="${r.playback}" var="p">
<g:if test="${p.type == 'slides'}">
${p.length}
</g:if>
</g:each>
</td>
<g:if test="${ismoderator}">
<td class="cell c5 lastcol" style="text-align:left;">
<g:if test="${r.published == 'true'}">
<a title="Hide" class="action-icon" href="${createLink(controller:'tool',action:'publish')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}"><img title="Hide" alt="Hide" class="smallicon" src="${resource(dir:'images',file:'hide.gif')}" /></a>
</g:if>
<g:else>
<a title="Show" class="action-icon" href="${createLink(controller:'tool',action:'publish')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}"><img title="Show" alt="Show" class="smallicon" src="${resource(dir:'images',file:'show.gif')}" /></a>
</g:else>
<a title="Delete" class="action-icon" onClick="if(confirm('Are you sure to delete this recording?')) window.location='${createLink(controller:'tool',action:'delete')}?bbb_recording_id=${r.recordID}'; return false;" href="#"><img title="Delete" alt="Delete" class="smallicon" src="${resource(dir:'images',file:'delete.gif')}" /></a>
</td>
</g:if>
</tr>
</g:if>
</g:each>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,60 @@
<html>
<head>
<title>BigBlueButton LTI Interface</title>
<meta name="layout" content="internal" />
</head>
<body>
<h1 style="margin-left:20px; text-align: center;"><a title="Join" class="action-icon" href="${createLink(controller:'tool',action:'join')}"><img title="Join" alt="Join the meeting" src="${resource(dir:'images',file:'bbb.jpg')}" /></a></h1>
<p style="margin-left:20px;width:80%"></p>
<br>
<table class="generaltable">
<thead>
<tr>
<th class="header c0" style="text-align:center;" scope="col">Recording</th>
<th class="header c1" style="text-align:center;" scope="col">Activity</th>
<th class="header c2" style="text-align:center;" scope="col">Description</th>
<th class="header c3" style="text-align:center;" scope="col">Date</th>
<th class="header c4" style="text-align:center;" scope="col">Duration</th>
<g:if test="${ismoderator}">
<th class="header c5 lastcol" style="text-align:left;" scope="col">Toolbar</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:each in="${r.playback}" var="p">
<a title="${p.type}" target="_new" href="${p.url}">${p.type}</a>&#32;
</g:each>
</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;">${new Date( Long.valueOf(r.startTime).longValue() )}</td>
<td class="cell c4" style="text-align:center;">
<g:each in="${r.playback}" var="p">
<g:if test="${p.type == 'slides'}">
${p.length}
</g:if>
</g:each>
</td>
<g:if test="${ismoderator}">
<td class="cell c5 lastcol" style="text-align:left;">
<g:if test="${r.published == 'true'}">
<a title="Hide" class="action-icon" href="${createLink(controller:'tool',action:'publish')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}"><img title="Hide" alt="Hide" class="smallicon" src="${resource(dir:'images',file:'hide.gif')}" /></a>
</g:if>
<g:else>
<a title="Show" class="action-icon" href="${createLink(controller:'tool',action:'publish')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}"><img title="Show" alt="Show" class="smallicon" src="${resource(dir:'images',file:'show.gif')}" /></a>
</g:else>
<a title="Delete" class="action-icon" onClick="if(confirm('Are you sure to delete this recording?')) window.location='${createLink(controller:'tool',action:'delete')}?bbb_recording_id=${r.recordID}'; return false;" href="#"><img title="Delete" alt="Delete" class="smallicon" src="${resource(dir:'images',file:'delete.gif')}" /></a>
</td>
</g:if>
</tr>
</g:if>
</g:each>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,60 @@
<html>
<head>
<title>BigBlueButton LTI Interface</title>
<link rel="stylesheet" href="${resource(dir:'css',file:'bootstrap.css')}" />
<link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
</head>
<body>
<h1 style="margin-left:20px; text-align: center;"><a title="Join" class="btn btn-primary btn-large" href="${createLink(controller:'tool',action:'join')}">Join Meeting</a></h1>
<br><br>
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th class="header c0" style="text-align:center;" scope="col">Recording</th>
<th class="header c1" style="text-align:center;" scope="col">Activity</th>
<th class="header c2" style="text-align:center;" scope="col">Description</th>
<th class="header c3" style="text-align:center;" scope="col">Date</th>
<th class="header c4" style="text-align:center;" scope="col">Duration</th>
<g:if test="${ismoderator}">
<th class="header c5 lastcol" style="text-align:left;" scope="col">Toolbar</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:each in="${r.playback}" var="p">
<a title="${p.type}" target="_new" href="${p.url}">${p.type}</a>&#32;
</g:each>
</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;">${new Date( Long.valueOf(r.startTime).longValue() )}</td>
<td class="cell c4" style="text-align:center;">
<g:each in="${r.playback}" var="p">
<g:if test="${p.type == 'slides'}">
${p.length}
</g:if>
</g:each>
</td>
<g:if test="${ismoderator}">
<td class="cell c5 lastcol" style="text-align:left;">
<g:if test="${r.published == 'true'}">
<a title="Hide" class="action-icon" href="${createLink(controller:'tool',action:'publish')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}"><img title="Hide" alt="Hide" class="smallicon" src="${resource(dir:'images',file:'hide.gif')}" /></a>
</g:if>
<g:else>
<a title="Show" class="action-icon" href="${createLink(controller:'tool',action:'publish')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}"><img title="Show" alt="Show" class="smallicon" src="${resource(dir:'images',file:'show.gif')}" /></a>
</g:else>
<a title="Delete" class="action-icon" onClick="if(confirm('Are you sure to delete this recording?')) window.location='${createLink(controller:'tool',action:'delete')}?bbb_recording_id=${r.recordID}'; return false;" href="#"><img title="Delete" alt="Delete" class="smallicon" src="${resource(dir:'images',file:'delete.gif')}" /></a>
</td>
</g:if>
</tr>
</g:if>
</g:each>
</tbody>
</table>
</body>
</html>

View File

@ -88,13 +88,13 @@ public class Proxy {
return this.url + API_SERVERPATH + APICALL_CREATE + "?" + url;
}
public String getJoinMeetingURL(String fullName, String meetingID, String password, String createTime, String userID) {
String url = getJoinMeetingURL(fullName, meetingID, password, createTime, userID, "" );
public String getJoinURL(String fullName, String meetingID, String password, String createTime, String userID) {
String url = getJoinURL(fullName, meetingID, password, createTime, userID, "" );
return url;
}
public String getJoinMeetingURL(String fullName, String meetingID, String password, String createTime, String userID, String webVoiceConf ) {
public String getJoinURL(String fullName, String meetingID, String password, String createTime, String userID, String webVoiceConf ) {
String url;
url = "fullName=" + getStringEncoded(fullName);
@ -117,7 +117,7 @@ public class Proxy {
return this.url + API_SERVERPATH + APICALL_ISMEETINGRUNNING + "?" + url;
}
public String getEndMeetingURL(String meetingID, String password) {
public String getEndURL(String meetingID, String password) {
String url = "meetingID=" + meetingID;
url += "&password=" + password;
@ -126,7 +126,7 @@ public class Proxy {
return this.url + API_SERVERPATH + APICALL_END + "?" + url;
}
public String getMeetingInfoURL(String meetingID, String password) {
public String getGetMeetingInfoURL(String meetingID, String password) {
String url = "meetingID=" + meetingID;
url += "&password=" + password;
@ -135,19 +135,19 @@ public class Proxy {
return this.url + API_SERVERPATH + APICALL_GETMEETINGINFO + "?" + url;
}
public String getMeetingsURL(String meetingID, String password) {
public String getGetMeetingsURL(String meetingID, String password) {
String url = getCheckSumParameterForQuery(APICALL_END, "");
return this.url + API_SERVERPATH + APICALL_END + "?" + url;
}
public String getRecordingsURL(String meetingID) {
public String getGetRecordingsURL(String meetingID) {
String url = "meetingID=" + meetingID;
url += getCheckSumParameterForQuery(APICALL_GETRECORDINGS, url);
String queryString = "meetingID=" + meetingID;
queryString += getCheckSumParameterForQuery(APICALL_GETRECORDINGS, queryString);
return this.url + API_SERVERPATH + APICALL_GETRECORDINGS + "?" + url;
return this.url + API_SERVERPATH + APICALL_GETRECORDINGS + "?" + queryString;
}
public String getPublishRecordingsURL(String recordID, String publish) {
@ -162,9 +162,9 @@ public class Proxy {
public String getDeleteRecordingsURL(String recordID) {
String url = "recordID=" + recordID;
url += getCheckSumParameterForQuery(APICALL_PUBLISHRECORDINGS, url);
url += getCheckSumParameterForQuery(APICALL_DELETERECORDINGS, url);
return this.url + API_SERVERPATH + APICALL_PUBLISHRECORDINGS + "?" + url;
return this.url + API_SERVERPATH + APICALL_DELETERECORDINGS + "?" + url;
}
public String getStringEncoded(String string){

View File

@ -47,8 +47,15 @@ public class Parameter {
public static final String TOOL_CONSUMER_INSTANCE_URL = "tool_consumer_instance_url";
public static final String CUSTOM_USER_ID = "custom_lis_person_sourcedid";
public static final String CUSTOM_WELCOME = "custom_welcome";
public static final String CUSTOM_RECORD = "custom_record";
//BigBlueButton custom parameters
public static final String CUSTOM_BBB_RECORD = "custom_bbb_record";
public static final String CUSTOM_BBB_VOICEBRIDGE = "custom_bbb_voicebridge";
public static final String CUSTOM_BBB_DURATION = "custom_bbb_duration";
public static final String CUSTOM_WELCOME = "custom_bbb_welcome";
///BigBlueButton internal parameters
public static final String BBB_RECORDING_ID = "bbb_recording_id";
public static final String BBB_RECORDING_PUBLISHED = "bbb_recording_published";
}

View File

@ -53,6 +53,7 @@ public class Role {
roles[i].equals(MENTOR) ||
roles[i].equals(URN_INSTITUTION_ROLE + MENTOR) ||
roles[i].equals(URN_CONTEXT_ROLE + MENTOR) ||
roles[i].equals(ADMINISTRATOR) ||
roles[i].equals(URN_INSTITUTION_ROLE + ADMINISTRATOR) ||
roles[i].equals(URN_CONTEXT_ROLE + ADMINISTRATOR)
){

6039
bbb-lti/web-app/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

0
bigbluebutton-client/resources/prod/BigBlueButton.html Executable file → Normal file
View File

View File

@ -537,7 +537,7 @@ while [ $# -gt 0 ]; do
echo " Customer: $CUSTOMER"
echo " Secret: $SECRET"
echo
ICON_URL=$( echo $LTI_URL | sed 's/tool.xml/images\/favicon.ico/')
ICON_URL=$( echo $LTI_URL | sed 's/tool/images\/favicon.ico/')
echo " Icon URL: $ICON_URL"
echo
echo