2012-10-16 02:36:54 +08:00
|
|
|
/*
|
|
|
|
BigBlueButton - http://www.bigbluebutton.org
|
|
|
|
|
|
|
|
Copyright (c) 2008-2012 by respective authors (see below). All rights reserved.
|
|
|
|
|
|
|
|
BigBlueButton 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 2 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, If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Author: Jesus Federico <jesus@blindsidenetworks.com>
|
|
|
|
*/
|
2012-10-16 23:22:45 +08:00
|
|
|
|
2012-10-11 23:52:41 +08:00
|
|
|
import java.util.ArrayList
|
|
|
|
import java.util.HashMap
|
|
|
|
import java.util.List
|
|
|
|
import java.util.Map
|
|
|
|
import java.util.Properties
|
|
|
|
|
2012-10-13 04:01:27 +08:00
|
|
|
import org.apache.commons.codec.digest.DigestUtils
|
|
|
|
|
2012-10-11 23:52:41 +08:00
|
|
|
import net.oauth.OAuthMessage
|
|
|
|
import net.oauth.signature.OAuthSignatureMethod;
|
|
|
|
import net.oauth.signature.HMAC_SHA1;
|
|
|
|
|
2012-10-16 23:22:45 +08:00
|
|
|
import BigbluebuttonService
|
|
|
|
import LtiService
|
2012-10-11 23:52:41 +08:00
|
|
|
|
|
|
|
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'
|
|
|
|
|
|
|
|
public static final String OAUTH_SIGNATURE = 'oauth_signature'
|
2012-10-16 23:43:20 +08:00
|
|
|
public static final String CONSUMER_ID = 'oauth_consumer_key'
|
2012-10-11 23:52:41 +08:00
|
|
|
public static final String USER_FULL_NAME = 'lis_person_name_full'
|
|
|
|
public static final String USER_LASTNAME = 'lis_person_name_family'
|
|
|
|
public static final String USER_EMAIL = 'lis_person_contact_email_primary'
|
|
|
|
public static final String USER_ID = 'lis_person_sourcedid'
|
|
|
|
public static final String USER_FIRSTNAME = 'lis_person_name_given'
|
|
|
|
public static final String COURSE_ID = 'context_id'
|
2012-10-16 02:50:21 +08:00
|
|
|
public static final String COURSE_TITLE = 'context_title'
|
2012-10-13 04:01:27 +08:00
|
|
|
public static final String RESOURCE_LINK_ID = 'resource_link_id'
|
|
|
|
public static final String RESOURCE_LINK_TITLE = 'resource_link_title'
|
|
|
|
public static final String RESOURCE_LINK_DESCRIPTION = 'resource_link_description'
|
|
|
|
public static final String ROLES = 'roles'
|
2012-10-16 02:36:54 +08:00
|
|
|
|
|
|
|
public static final String LAUNCH_LOCALE = "launch_presentation_locale"
|
|
|
|
public static final String LAUNCH_DOCUMENT_TARGET = "launch_presentation_document_target"
|
|
|
|
public static final String LAUNCH_CSS_URL = "launch_presentation_css_url"
|
|
|
|
public static final String LAUNCH_RETURN_URL = "launch_presentation_return_url"
|
|
|
|
|
2012-10-11 23:52:41 +08:00
|
|
|
public static final String CUSTOM_USER_ID = 'custom_lis_person_sourcedid'
|
|
|
|
|
|
|
|
LtiService ltiService
|
2012-10-13 04:01:27 +08:00
|
|
|
BigbluebuttonService bigbluebuttonService
|
|
|
|
|
2012-10-11 23:52:41 +08:00
|
|
|
def index = {
|
2012-10-17 00:14:16 +08:00
|
|
|
if( ltiService.consumerMap == null) ltiService.initConsumerMap()
|
|
|
|
log.debug CONTROLLER_NAME + "#index" + ltiService.consumerMap
|
2012-10-11 23:52:41 +08:00
|
|
|
|
|
|
|
def resultMessageKey = "init"
|
|
|
|
def resultMessage = "init"
|
|
|
|
def success = false
|
2012-10-16 02:36:54 +08:00
|
|
|
def consumer
|
2012-10-11 23:52:41 +08:00
|
|
|
ArrayList<String> missingParams = new ArrayList<String>()
|
|
|
|
log.debug "Checking for required parameters"
|
|
|
|
if (hasAllRequiredParams(params, missingParams)) {
|
|
|
|
def sanitizedParams = sanitizePrametersForBaseString(params)
|
|
|
|
|
2012-10-17 00:14:16 +08:00
|
|
|
consumer = ltiService.getConsumer(params.get(CONSUMER_ID))
|
2012-10-16 02:36:54 +08:00
|
|
|
if (consumer != null) {
|
2012-10-16 23:43:20 +08:00
|
|
|
log.debug "Found consumer with key " + consumer.get("key")
|
|
|
|
if (checkValidSignature(request.getMethod().toUpperCase(), retrieveLtiEndpoint(), consumer.get("secret"), sanitizedParams, params.get(OAUTH_SIGNATURE))) {
|
2012-10-16 02:36:54 +08:00
|
|
|
if (hasValidStudentId(params, consumer)) {
|
2012-10-16 23:43:20 +08:00
|
|
|
log.debug "The message has a valid signature."
|
|
|
|
//Localize the default welcome message
|
2012-10-16 03:43:31 +08:00
|
|
|
session['org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE'] = new Locale(params.get(LAUNCH_LOCALE))
|
|
|
|
String welcome = message(code: "bigbluebutton.welcome", args: [params.get(RESOURCE_LINK_TITLE), params.get(COURSE_TITLE)])
|
2012-10-16 23:43:20 +08:00
|
|
|
log.debug "Localized default welcome message: " + welcome
|
|
|
|
|
2012-10-16 02:36:54 +08:00
|
|
|
String destinationURL = bigbluebuttonService.getJoinURL(params.get(RESOURCE_LINK_TITLE),
|
|
|
|
params.get(RESOURCE_LINK_ID),
|
|
|
|
DigestUtils.shaHex("ap" + params.get(RESOURCE_LINK_ID)),
|
|
|
|
DigestUtils.shaHex("mp"+params.get(RESOURCE_LINK_ID)),
|
2012-10-16 02:50:21 +08:00
|
|
|
welcome,
|
2012-10-16 02:36:54 +08:00
|
|
|
params.get(LAUNCH_RETURN_URL),
|
|
|
|
params.get(USER_FULL_NAME), params.get(ROLES))
|
2012-10-13 04:01:27 +08:00
|
|
|
|
|
|
|
log.debug "redirecting to " + destinationURL
|
|
|
|
if( destinationURL != null ) {
|
|
|
|
success = true
|
|
|
|
redirect(url:destinationURL)
|
|
|
|
} else {
|
|
|
|
resultMessageKey = 'BigBlueButtonServerError'
|
|
|
|
resultMessage = "The join could not be completed"
|
|
|
|
log.debug resultMessage
|
|
|
|
}
|
2012-10-11 23:52:41 +08:00
|
|
|
|
|
|
|
} else {
|
|
|
|
resultMessageKey = 'InvalidStudentId'
|
|
|
|
resultMessage = "Can not determine user because of missing student id or email."
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
resultMessageKey = 'InvalidSignature'
|
|
|
|
resultMessage = "Invalid signature (" + params.get(OAUTH_SIGNATURE) + ")."
|
|
|
|
log.debug resultMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
resultMessageKey = 'CustomerNotFound'
|
2012-10-16 23:43:20 +08:00
|
|
|
resultMessage = "Customer with id = " + params.get(CONSUMER_ID) + " was not found."
|
2012-10-11 23:52:41 +08:00
|
|
|
log.debug resultMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
resultMessageKey = 'MissingRequiredParameter'
|
|
|
|
String missingStr = ""
|
|
|
|
for(String str:missingParams)
|
|
|
|
missingStr += str + ", ";
|
|
|
|
|
|
|
|
resultMessage = "Missing parameters [$missingStr]"
|
|
|
|
log.debug resultMessage
|
|
|
|
}
|
|
|
|
|
2012-10-13 04:01:27 +08:00
|
|
|
if (!success) {
|
2012-10-11 23:52:41 +08:00
|
|
|
log.debug "Error"
|
|
|
|
response.addHeader("Cache-Control", "no-cache")
|
|
|
|
withFormat {
|
|
|
|
xml {
|
|
|
|
render(contentType:"text/xml") {
|
|
|
|
response() {
|
|
|
|
returncode(success)
|
|
|
|
messageKey(resultMessageKey)
|
|
|
|
message(resultMessage)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-10-13 04:01:27 +08:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2012-10-11 23:52:41 +08:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2012-10-17 00:14:16 +08:00
|
|
|
def retrieveLtiEndpoint() {
|
2012-10-17 01:46:29 +08:00
|
|
|
String endPoint = ltiService.endPoint
|
|
|
|
return endPoint
|
2012-10-17 00:14:16 +08:00
|
|
|
}
|
|
|
|
|
2012-10-11 23:52:41 +08:00
|
|
|
def test = {
|
|
|
|
log.debug CONTROLLER_NAME + "#index"
|
|
|
|
|
|
|
|
response.addHeader("Cache-Control", "no-cache")
|
|
|
|
withFormat {
|
|
|
|
xml {
|
|
|
|
render(contentType:"text/xml") {
|
|
|
|
response() {
|
|
|
|
returncode(false)
|
|
|
|
messageKey('RequestInvalid')
|
|
|
|
message('The request is not supported.')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2012-10-16 23:43:20 +08:00
|
|
|
private Properties sanitizePrametersForBaseString(Object params) {
|
2012-10-11 23:52:41 +08:00
|
|
|
|
|
|
|
Properties reqProp = new Properties();
|
|
|
|
for (String key : ((Map<String, String>)params).keySet()) {
|
|
|
|
if (key == "action" || key == "controller") {
|
|
|
|
// 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
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
reqProp.setProperty(key, ((Map<String, String>)params).get(key));
|
|
|
|
}
|
|
|
|
|
|
|
|
return reqProp
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if all required parameters have been passed in the request.
|
|
|
|
* @param params - the HTTP request parameters
|
|
|
|
* @param missingParams - a list of missing parameters
|
|
|
|
* @return - true if all required parameters have been passed in
|
|
|
|
*/
|
2012-10-16 23:43:20 +08:00
|
|
|
private boolean hasAllRequiredParams(Object params, Object missingParams) {
|
2012-10-11 23:52:41 +08:00
|
|
|
boolean hasAllParams = true
|
2012-10-16 23:43:20 +08:00
|
|
|
if (! ((Map<String, String>)params).containsKey(CONSUMER_ID)) {
|
|
|
|
((ArrayList<String>)missingParams).add(CONSUMER_ID);
|
2012-10-11 23:52:41 +08:00
|
|
|
hasAllParams = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! ((Map<String, String>)params).containsKey(USER_ID) && ! ((Map<String, String>)params).containsKey(CUSTOM_USER_ID)) {
|
|
|
|
if (! ((Map<String, String>)params).containsKey(USER_EMAIL)) {
|
|
|
|
((ArrayList<String>)missingParams).add(USER_EMAIL);
|
|
|
|
if (! ((Map<String, String>)params).containsKey(USER_ID)) {
|
|
|
|
((ArrayList<String>)missingParams).add(USER_ID);
|
|
|
|
} else {
|
|
|
|
((ArrayList<String>)missingParams).add(CUSTOM_USER_ID);
|
|
|
|
}
|
|
|
|
|
|
|
|
hasAllParams = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! ((Map<String, String>)params).containsKey(COURSE_ID)) {
|
|
|
|
((ArrayList<String>)missingParams).add(COURSE_ID);
|
|
|
|
hasAllParams = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! ((Map<String, String>)params).containsKey(OAUTH_SIGNATURE)) {
|
|
|
|
((ArrayList<String>)missingParams).add(OAUTH_SIGNATURE);
|
|
|
|
hasAllParams = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return hasAllParams
|
|
|
|
}
|
|
|
|
|
2012-10-16 02:36:54 +08:00
|
|
|
private boolean hasValidStudentId(params, consumer) {
|
2012-10-11 23:52:41 +08:00
|
|
|
if (((Map<String, String>)params).containsKey(USER_ID) || ((Map<String, String>)params).containsKey(CUSTOM_USER_ID)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (((Map<String, String>)params).containsKey(USER_EMAIL)) {
|
2012-10-16 02:36:54 +08:00
|
|
|
((Map<String, String>)params).put(USER_ID, ((Map<String, String>)consumer).get(USER_EMAIL))
|
2012-10-11 23:52:41 +08:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the passed signature is valid.
|
|
|
|
* @param method - POST or GET method used to make the request
|
|
|
|
* @param URL - The target URL for the Basic LTI tool
|
|
|
|
* @param conSecret - The consumer secret key
|
|
|
|
* @param postProp - the parameters passed in from the tool
|
|
|
|
* @param signature - the passed in signature calculated from the client
|
|
|
|
* @return - TRUE if the signatures matches the calculated signature
|
|
|
|
*/
|
2012-10-16 23:43:20 +08:00
|
|
|
private boolean checkValidSignature(String method, String URL, String conSecret, Object postProp, String signature) {
|
2012-10-11 23:52:41 +08:00
|
|
|
OAuthMessage oam = new OAuthMessage(method, URL, ((Properties)postProp).entrySet());
|
|
|
|
HMAC_SHA1 hmac = new HMAC_SHA1();
|
|
|
|
hmac.setConsumerSecret(conSecret);
|
|
|
|
|
2012-10-16 02:36:54 +08:00
|
|
|
//log.debug("Base Message String = [ " + hmac.getBaseString(oam) + " ]\n");
|
2012-10-11 23:52:41 +08:00
|
|
|
String calculatedSignature = hmac.getSignature(hmac.getBaseString(oam))
|
2012-10-16 02:36:54 +08:00
|
|
|
//log.debug("Calculated: " + calculatedSignature + " Received: " + signature);
|
2012-10-11 23:52:41 +08:00
|
|
|
return calculatedSignature.equals(signature)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|